Python 语法杂记
本文主要记录一些学习过程中遇到的一些比较零碎的 python 语法知识。
常见易错用法
for 循环中修改下标的值
python 中的 for 循环一般会写成这样1
2for i in range(10):
....
如果要达到修改 i 的值跳过一些值不处理,建议使用 while
语句。
字典
初始化
可通过 {}
或 dict()
函数进行初始化,通过 dict()
初始化时,可以选择是否传入参数,传入参数初始化时,参数格式为包含若干 kv 的一个 list,每个 kv 用一个 tuple 表示,如1
2
3
4
5
6
7
8>>>d = dict(
[('foozelator', 123),
('frombicator', 18),
('spatzleblock', 34),
('snitzelhogen', 23)
])
>>>d
{'foozelator': 123, 'frombicator': 18, 'snitzelhogen': 23, 'spatzleblock': 34}
删除一个 key
- 从字典中删除一个 key:dict.pop(key[, default])
, 存在 key 时返回 key 对应的 value,不存在时返回 default。不存在且没有 default 时返回 KeyError。
遍历字典
dict.keys()
返回字典 dict 所有 keys 组成的一个 listdict.values()
返回字典 dict 所有 values 组成的一个 listdict.items()
返回字典 dict 所有 kv 组成的一个 list,kv 以 tuple 的形式存储
对字典排序
通过 sorted 函数可以根据字典的 key 或 value 对字典排序,并返回一个元素类型为 tuple 为的 list,每个 tuple 代表字典中的一个元素。排序不会改变原来字典中的值。1
2
3
4
5
6
7
8a = {1:2,2:1}
# 根据key对字典排序,reverse = True表示从大到小,默认是从小到大
sorted(a.items(), key = lambda x:x[0], reverse = True)
# 输出为[(2,1),(1,2)]
# 根据value对字典从小到大排序
sorted(a.items(), key = lambda x:x[1])
# 输出为[(2:1),(1:2)]
集合
可变集合
用 {} 或 set () 函数来生成可变集合,集合中不含有相同元素。1
2
3s={} # 非法
s={1,2,3,4} # 合法
s=set() # 也可用s = set(list),用一个集合提取list中的不重复元素
创建一个不可变集合1
2
3>>>s = frozenset([1, 2, 3, 'a', 1])
>>>s
frozenset({1, 2, 3, 'a'})1
2
3
4
5
6
7
8
9>>>flight_distance = {}
>>>city_pair = frozenset(['Los Angeles', 'New York'])
>>>flight_distance[city_pair] = 2498
>>>flight_distance[frozenset(['Austin', 'Los Angeles'])] = 1233
>>>flight_distance[frozenset(['Austin', 'New York'])] = 1515
>>>flight_distance
{frozenset({'Austin', 'New York'}): 1515,
frozenset({'Austin', 'Los Angeles'}): 1233,
frozenset({'Los Angeles', 'New York'}): 2498}
集合的一些方法
- 添加元素,
s.add(item)
- 交集,
s1&s2
或s1.intersection(s2)
, 返回集合 s1 和集合 s2 的交集 - 并集,
s1|s2
或s1.union(s2)
, 返回集合 s1 和集合 s2 的并集 - 差集,
s1-s2
或s1.defference(s2)
返回 s1 中有但 s2 中没有的元素的集合 - 对称差集,
s1^s2
或s1.symmetric_difference(s2)
返回 s1 中有但 s2 中没有的元素和 s2 中有但 s1 中没有的元素的合集 - 子集,
s1.issubset(s2)
或s1<=s2
判断 s1 是否 s2 的子集;反之也可用s2.issuperset(s1)
达到上面的效果 - 删除一个元素
s.remove(element)
或s.pop(element)
后者会返回这个值元素的值而前者不会;不存在该元素时均会报错。s.discard(element)
作用跟 remove 一样,区别在于不存在该元素时 discard () 不会报错
列表
列表合并
可通过加号 +
按顺序合并两个列表,如1
2
3
4>>>a = [1, 2, 3]
>>>b = [3.2, 'hello']
>>>a + b
[1, 2, 3, 3.2, 'hello']extend()
方法也能实现相同功能。如: 1
2
3
4
5>>>a = [1, 2, 3]
>>>b = [3.2, 'hello']
>>>a.extend(b)
>>>a
[1, 2, 3, 3.2, 'hello']
列表排序
列表可用内置函数,分为两种类型:排序后改变原列表和排序后不改变列表
排序后改变原列表的方法是 listName.sort()
, 不改变原列表的方法是 sorted(listName)
。具体见下面例子1
2
3
4
5
6
7
8
9>>>s = [1,4,3,2]
>>>s.sort()
>>>s
[1, 2, 3, 4]
>>>s = [1,4,3,2]
>>>sorted(s)
[1, 2, 3, 4]
>>>s
[1, 4, 3, 2]reverse=True
的参数即可 1
2
3
4
5
6
7
8>>>s=[1, 4, 3, 2]
>>>sorted(s,reverse=True)
[4, 3, 2, 1]
>>>s
[1, 4, 3, 2]
>>>s.sort(reverse=True)
>>>s
[4, 3, 2, 1]
列表推导式 (List comprehension)
也叫列表生成式。
将多条语句写成一条,如要求列表 a 中所有偶数的和,可写成1
2a = [1,2,3,4,5,6]
print sum([i for i in a if i%2==0])[i for i in a if i%2==0]
则是列表推导式,该语句生成了列表[2,4,6]
但是,Python 会生成这个列表,然后再将它放到垃圾回收机制中(因为没有变量指向它),这毫无疑问是种浪费。
为了解决这种问题,与 rang()
和 xrange()
的问题类似,Python 使用生成器(generator)表达式来解决这个问题:
将 sum 中代表 list 的括号去掉即可,修改后如下所示:1
2a = [1,2,3,4,5,6]
print sum(i for i in a if i%2==0)(i for i in a if i%2==0)
就是一个生成器,与列表生成式最大的不同是列表生成式会在执行语句的时候生成完整的列表,而生成器会在在循环的过程中不断推算出后续的元素
除了上面这种定义生成器的方法,还可以在函数中通过 yield
关键字实现一个生成器。如下面生成斐波那契数列的例子:1
2
3
4
5
6
7
8def print_fibona(n):
a,b=0,1,
for i in range(n):
yield b
a,b = b,a+b
for i in print_fibona(8):
print i,1
1 1 2 3 5 8 13 21
print_fibona
不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。
列表的其他一些方法
- 查找某一元素在列表中出现了几次,
list.count(element)
返回 element 在 list 中出现的次数 - 查找某一元素在列表中第一次出现的位置,
list.index(element)
返回 element 在 list 中第一次出现的位置,不存在 element 元素时会报错 - 在特定位置插入某一元素,其他元素依次往后移动一步,
list.insert(index,element)
在 index 处插入 element,原来在 index 处及后面的元素依次往后移动一位 - 删除元素,有两种方法,
list.remove(element)
会将 list 中第一次出现的 element 删除;list.pop(index)
则会将 list 中下标为 index 的元素删除且返回该元素的值。 - 列表反转,
list.reverse()
回将 list 中的元素反转
map 方法生成序列
可以通过 map 的方式利用函数来生成序列,例子如下:1
2
3
4
5def sqr(x):
return x ** 2
a = [2,3,4]
print map(sqr, a)
输出为 [4, 9, 16]
其用法为 map(aFun, aSeq)
, 将函数 aFun
应用到序列 aSeq
上的每一个元素上,返回一个列表,不管这个序列原来是什么类型。
事实上,根据函数参数的多少,map 可以接受多组序列,将其对应的元素作为参数传入函数,例子如下:1
2
3
4
5
6def add(x, y):
return x + y
a = (2,3,4)
b = [10,5,3]
print map(add,a,b)[12, 8, 7]
序列赋值
序列(list,tuple,str) 可以将其值逐一赋值给变量,详见下面的例子1
2
3
4
5
6
7
8
91,2,3] a,b,c=[
print a,b,c
1 2 3
1,2,3) a,b,c=(
print a,b,c
1 2 3
"123" a,b,c=
print a,b,c
1 2 3
数值
整形(int)和长整形(long)
整型数字的最大最小值
- 在 32 位系统中,一个整型 4 个字节,最小值 -2,147,483,648,最大值 2,147,483,647。
- 在 64 位系统中,一个整型 8 个字节,最小值 -9,223,372,036,854,775,808,最大值 9,223,372,036,854,775,807。
当整型超出范围时,Python 会自动将整型转化为长整型,长整型就是在数字后面加上一个大写的 L
复数
Python 使用 j 来表示复数的虚部:1
2
3
4
5a = 1 + 2j
type(a)
a.real # 实部
a.imag # 虚部
a.conjugate() # 共轭abs(n)
求n的绝对值 round(n)
求n的整数部分,返回的是float类型 max(n,m)
求m,n的最大值 min(n,m)
求m,n的最小值
其他的一些表示方法
- 科学计数法,
1e-6
表示 \(10^{-6}\) - 16 进制,前面加上
0x
修饰,后面的数字范围为 0~F - 8 进制,前面加上
0
修饰,后面的数字范围为 0~7 - 2 进制,前面加上
0b
修饰,后面数字范围为 0~1
字符串
常用的字符串方法
s.split(c)
, 以符号 c 为分隔符将字符串 s 分割,返回字符串列表c.join(sList)
,作用跟上面的相反,以符号 c 为连接符将字符串数组 sList 连接起来s.repalce(a,b)
, 将字符串中的 a 替换为 b,并返回替换后的字符串,注意 s 本身不变s.upper()
,将 s 中的英文字母转为大写的并返回,但是 s 本身不变s.lower()
,将 s 中的英文字母转为小写的并返回,但是 s 本身不变s.strip()
,去掉字符串 s 前后的空格并返回,但是 s 本身不变s.lstrip()
,去掉字符串 s 前的空格并返回,但是 s 本身不变s.rstrip()
,去掉字符串 s 后的空格并返回,但是 s 本身不变 可通过dir(str)
查找更多方法
数字与字符的转换
整数转字符串
- 16 进制:
hex(255)
返回 '0xff' - 8 进制:
oct(255)
返回 '0377' - 2 进制:
bin(255)
返回 '0b11111111'
字符串转整数
通过 int(s)
转换,还可以指定特定的进制,默认是十进制。如下面的方法均返回 255
- int('ff',16)
- int('377',8)
- int('111111111',2)
- int('255')
ASCII 码与字符的转换
- 数字转 ASCII 码:
chr(97) --> 'a'
- ASCII 码转数字:
ord('A') --> 65
字符串的分片与索引
索引指的是可以通过下标来寻找字符串中的某个字符,0 下标代表第一个,-1 下标代表倒数第一个,-2 下标代表倒数第二个
分片指的是提取子字符串,一般格式为 [start:end:step]
,start 和 end 都是指字符串的下标,省略时默认为字符串的头和尾;step 指每次取字符串的步长,省略时为 1,也即是从 start 到 end-1 每个字符串都取,step 也可取负值,表示从后往前按 step 的绝对值来取。如 s[::-1]
表示反转字符串
函数
高阶函数(Higher-order function)
把另外一个函数作为参数传入的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
map/reduce 函数
- map 函数:两个参数,第一个参数为接收一个参数的函数,第二个参数为一个序列,利用第一个参数所代表的函数对序列中的每个元素操作,返回操作后的序列
- reduce 函数:两个参数,第一个参数为接收两个参数的函数,第二个参数为一个序列,利用第一个参数代表的函数对序列中的两个首元素操作,返回的结果与序列的下一元素再进行函数的操作,直到遍历完序列。
例子:1
2
3
4
5
6
7# 利用map函数对列表中每个数进行平方操作
map(lambda x:x**2,[1,2,3])
[1, 4, 9]
# 利用reduce函数实现sum()函数的功能
lambda x,y:x+y),[1,2,3,4,5]) reduce((
15lambda
函数,也可以将lambda函数改成 def
定义好的函数。
filter 函数
Python 内建的 filter () 函数用于过滤序列。 和 map () 类似,filter () 也接收一个函数和一个序列。和 map () 不同的时,filter () 把传入的函数依次作用于每个元素,然后根据返回值是 True 还是 False 决定保留还是丢弃该元素。
例子:过滤掉 1~100 中的素数并返回结果1
2
3
4
5
6
7import math
def is_prime(num):
for i in range(2,int(math.sqrt(num))+1):
if num%i == 0:
return True
print filter(is_prime,range(1,101))
sorted 函数
sorted 函数除了可以用来给列表排序外,还可以通过排序函数作为传入参数,进行指定的排序。
排序函数通常规定,对于两个元素 x 和 y,如果认为 x < y,则返回 - 1 或负数,如果认为 x == y,则返回 0,如果认为 x > y,则返回 1 或正数。python 内部定义的排序函数规则就是这样的,根据这样的原理,我们可以自定义一个排序函数进行降序排序。例子如下:1
2
3
4
5
6
7def descend_sort(x,y):
if x>y:
return -1
else:
return 1
print sorted(range(1,101),descend_sort)
上面的代码也可以简单写成 sorted(range(1,101),lambda x,y:y-x)
返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回1
2
3
4
5
6
7def lazy_sum(*args):
def sum():
s = 0
for i in args:
s+=i
return s
return sum
调用 lazy_sum () 时,返回的并不是求和结果,而是求和函数:1
2
31, 3, 5, 7, 9) f = lazy_sum(
f
<function sum at 0x10452f668>
调用函数 f 时,才真正计算求和的结果:1
2 f()
25
在这个例子中,我们在函数 lazy_sum
中又定义了函数 sum
,并且,内部函数 sum
可以引用外部函数 lazy_sum
的参数和局部变量,当 lazy_sum
返回函数 sum
时,相关参数和变量都保存在返回的函数中,这种程序称为闭包(Closure)
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了 f () 才执行。例子如下1
2
3
4
5
6
7
8
9def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
你可能认为调用 f1 (),f2 () 和 f3 ( ) 结果应该是 1,4,9,但实际结果是:1
2>>>print f1(),f2(),f3()
9 9 9
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。如下面的例子1
2
3
4
5
6def count():
fs = [lambda x=y:x**2 for y in range(1,4)]
return fs
f1,f2,f3 = count()
print f1(),f2(),f3()1 4 9
装饰器
有一个函数我们希望将其运行前后打印某些信息,却又不希望改变这个函数的代码,那么久可以通过装饰器(decorator)来实现这个功能。 例子如下:1
2
3
4
5
6
7
8
9
10
11def log(func):
def wrapper():
print 'before %s' %func.__name__
return func()
return wrapper
def hello():
print 'hello'
hello()1
2before hello_world
hello__name__
属性变为了wrapper的__name__
属性。也就是加入在上面的程序的最后加上 print hello_world.__name__
打印出来的是 wrapper
。所以,**需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错**。
这些事情不用我们自己做,Python 内置的 functools.wraps
就是干这个事的,所以,上面的规范写法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14import functools
def log(func):
def wrapper():
print 'before %s' %func.__name__
return func()
return wrapper
def hello_world():
print 'hello'
hello_world()
print hello_world.__name__1
2
3before hello_world
hello
hello_world
上面的例子中装饰器均没有参数,下面给出装饰器带有参数的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def log_a(text):
def decorator_a(func):
def wrapper_a():
print 'args in the decorator is %s'%text
func()
return wrapper_a
return decorator_a
def hello_world_a():
print 'hello'
hello_world_a()
print hello_world_a.__name__1
2
3args in the decorator is haha
hello
hello_world_ahello_world_a()
相当于执行了 hello_world_a()=log('haha')(hello_world_a())
,其中的 log('haha')
返回了装饰器函数 decorator_a
.
类与对象
私有变量
python 中没有 private 关键字来限定变量的私有性,假如变量名是以两根下划线开头,那么就认为是私有变量,如为类 s 定义了一个__age
的变量,那么不能通过 s.__age
在外部修改这个变量,只能通过在类的内部定义 set 和 get 方法。
获取对象的信息
通过 type(object)
函数或 isinstance(object,type)
函数可以判断一个类或对象的类型,通过 dir(object)
函数可以找到一个对象的所有属性和方法。通过 hasattr(object, 'x')
判断 object 是否有属性 x
通过 dir(object)
列出一个类的所有属性和方法会发现有很多__XXX___
方法,类似__xxx__
的属性和方法在 Python 中都是有特殊用途的,比如__len__
方法返回长度。在 Python 中,如果你调用 len () 函数试图获取一个对象的长度,实际上,在 len () 函数内部,它自动去调用该对象的__len__() 方法,所以,下面的代码是等价的:1
2
3
4len('ABC')
3
'ABC'.__len__()
3
动态绑定属性和方法
定义了一个 class,或者创建了一个 class 的实例后,我们可以给该类或实例绑定任何属性和方法,这就是动态语言的灵活性。如下面的例子 先定义一个类1
2
3class Student(object):
pass
...1
2
3
4 s = Student()
'hello' # 动态给实例绑定一个属性 s.name =
print s.name
hello__slots__
变量,变量的内容设为能够动态绑定的属性即可。
隐藏 getter 和 setter 为类的属性
通过装饰器 @property
可以隐藏类对某个属性的 get 方法和 set 方法,见下面的例子1
2
3
4
5
6
7
8
9
10
11
12class Student(object):
def birth(self):
return self._birth
def birth(self, value):
self._birth = value
def age(self):
return 2014 - self._birth@property
就可以了,此时,@property 本身又创建了另一个装饰器@birth.setter
,这个装饰器负责把一个setter方法变成属性赋值,并且在这个方法内可以限制复制的的范围等。
调用方法如下所示1
2
3
4>>>s = Student()
>>>s.birth = 2001
>>>print s.birth,s.age
2001 13s.birth = 2001
实际上是执行了装饰器 @birth.setter
装饰的birth方法,因此可在这个方法内加上赋值的限制条件,过滤不合法的赋值。
也可以将一个属性定义为只读属性,只定义 getter 方法即可。如上面的 age 方法。
类的一些内部函数
__str__
该函数是在直接打印对象时输出的内容,如下例子所示1
2
3
4
5class Student(object):
pass
s = Student()
print s<__main__.Student object at 0x02124DF0>
,表示对象在内存中的地址,可以重写这个函数的输出,见下面的例子 1
2
3
4
5
6class Student(object):
def __str__(self):
return 'object student'
s = Student()
print sobject student
。
__repr__
该函数与__str__
函数很类似,只是在直接显示变量调用的不是__str__()
,而是__repr__()
,两者的作用的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务。1
2
3
4
5
6
7`>>> class Student(object):
def __str__(self):
return 'object student'
... ...
s=Student()
s
<__main__.Student object at 0x02859930>
__iter__
如果一个类想被用于 for ... in
循环,类似 list 或 tuple 那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python 的 for 循环就会不断调用该迭代对象的 next () 方法拿到循环的下一个值,直到遇到 StopIteration 错误时退出循环。
以斐波那契数列为例,写一个 Fib 类,可以作用于 for 循环:1
2
3
4
5
6
7
8
9
10
11
12class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def next(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration();
return self.a # 返回下一个值1
2
3
4
5
6
7
8
9
10
11for n in Fib():
print n
...
1
1
2
3
5
...
46368
75025
__getitem__
Fib 实例虽然能作用于 for 循环,看起来和 list 有点像,但是,把它当成 list 来使用还是不行,比如,取第 5 个元素:1
2
3
45] Fib()[
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
```
现在,就可以按下标访问数列的任意一项了:
```py
f = Fib()
0] f[
1
1] f[
1
2] f[
2
3] f[
3
__call__
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用 instance.method () 来调用。能不能直接在实例本身上调用呢?类似 instance ()?在 Python 中,答案是肯定的。
任何类,只需要定义一个__call__()
方法,就可以直接对实例进行调用。请看示例:1
2
3
4
5
6class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
调用方式如下:1
2
3'Michael') s = Student(
s()
My name is Michael.
__call__()
还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样
判断一个对象是否能被调用,能被调用的对象就是一个 Callable 对象,比如函数和我们上面定义的带有__call ()__的类实例:1
2
3
4
5
6
7
8
9
10callable(Student())
True
callable(max)
True
callable([1, 2, 3])
False
callable(None)
False
callable('string')
False
错误、调试和测试
概念
常用的调试结构1
2
3
4
5
6
7try
....
except
...
else # 没有捕捉到exception时执行该语句
....
finally
Python 所有的错误都是从 BaseException
类派生的,常见的错误类型和继承关系看这里: https://docs.python.org/2/library/exceptions.html#exception-hierarchy
logging 模块可以把错误记录到日志文件里,方便事后排查
抛出错误
因为错误是 class,捕获一个错误就是捕获到该 class 的一个实例。因此,错误并不是凭空产生的,而是有意创建并抛出的。Python 的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。
如果要抛出错误,首先根据需要,可以定义一个错误的 class,选择好继承关系,然后,用 raise
语句抛出一个错误的实例:1
2
3
4
5
6
7
8class FooError(StandardError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
只有在必要的时候才定义我们自己的错误类型。如果可以选择 Python 已有的内置的错误类型(比如 ValueError,TypeError),尽量使用 Python 内置的错误类型。
另一种错误处理的方式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def foo(s):
n = int(s)
return 10 / n
def bar(s):
try:
return foo(s) * 2
except StandardError, e:
print 'Error!'
raise
def main():
bar('0')
main()
在 bar () 函数中,我们明明已经捕获了错误,但是,打印一个 Error! 后,又把错误通过 raise 语句抛出去了,这不有病么?
其实这种错误处理方式不但没病,而且相当常见。捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。
raise 语句如果不带参数,就会把当前错误原样抛出。此外,在 except 中 raise 一个 Error,还可以把一种类型的错误转化成另一种类型:1
2
3
4try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
只要是合理的转换逻辑就可以,但是,决不应该把一个 IOError 转换成毫不相干的 ValueError。
调试
print、assert 语句
最基础的调试就是通过 print
语句打印出变量的值,但是这样每次调试后都要注释或删除 print
语句。
因此也可使用 assert
语句,该语句的结构为 assert condition,'message'
, 只有当 condition 为 False 时,才会抛出一个 AssertionError
并打印出 message
logging 模块
和 assert 比,logging 不会抛出错误,而且可以输出到文件。并且可以指定输出的信息的级别,包括有 debug,info,warning,error
等几个级别1
2
3
4
5
6import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print 10 / n
上面的 logging.basicConfig
就是设置输出的日志的等级,logging.info
为输出的内容。
输出的内容如下:1
2
3
4
5INFO:root:n = 0
Traceback (most recent call last):
File "XX.py", line X, in <module>
print 10 / n
ZeroDivisionError: integer division or modulo by zero
pdb
pdb (Python Debugger) 是 Python 的调试器,可以让程序以单步方式运行,并随时查看运行状态。
通过 python -m pdb XXX.py
可以启动调试器调试 XXX.py
,n
命令执行当前代码并转到下一行,p 变量名
打印出具体的变量,q
命令退出调试程序。
除了上面的使用方法,还可以在可能出错的地方放一个 pdb.set_trace()
,相当于设置一个断点。运行代码时,程序会自动在 pdb.set_trace()
暂停并进入 pdb 调试环境,可以用命令 p
查看变量,或者用命令 c
继续运行
多进程和多线程
多进程
python 提供的跨平台多进程模块为 multiprocessing
, 使用的方式如下:1
2
3
4
5
6
7
8from multiprocessing import Process
def target_func(arg1,arg2):
....
p1 = Process(target=target_func,args=(arg1,arg2))
p2 = Process()
p1.start()
p1.join()target_func
,注意同时执行任务的最大进程数等于该机器的核数。
更详细的用法参考这篇文章 ### 多线程 python 提供的多线程模块为 thread
模块和 threading
模块,后者是高级模块,除了封装了前者还封装了很多其他方法。
一般的使用有两种:1)继承 threading.Thread
构造自己的线程类。2)类似多进程将需要执行的任务作为参数构造线程。
详细的语法可参考这篇文章。需要注意的是多线程同时修改进程中的公共变量时记得加线程锁。
ThreadLocal
在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
但是局部变量也有问题,就是在函数调用的时候必须要通过参数传递。如下面的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14import threading
def process_student(name):
print 'Hello, %s (in %s)' % (name, threading.current_thread().name)
def process_thread(name):
process_student(name)
t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import threading
# 创建全局ThreadLocal对象:
local_school = threading.local()
def process_student():
print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name)
def process_thread(name):
# 绑定ThreadLocal的student:
local_school.student = name
process_student()
t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
全局变量local_school
就是一个 ThreadLocal 对象,每个 Thread 对它都可以读写 student 属性,但互不影响。你可以把 local_school
看成全局变量,但每个属性如 local_school.student
都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal 内部会处理。
可以理解为全局变量 local_school
是一个 dict,不但可以用 local_school.student
,还可以绑定其他变量,如 local_school.teacher
等等。
ThreadLocal 最常用的地方就是为每个线程绑定一个数据库连接,HTTP 请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
常用内建模块
collections
collections 提供了许多有用的集合类
namedtuple namedtuple 是一个函数,它用来创建一个自定义的 tuple 对象,并且规定了 tuple 元素的个数,并可以用属性而不是索引来引用 tuple 的某个元素
1
2
3
4
5
6
7>>>from collections import namedtuple
>>>Coordinate = namedtuple("corr",['x','y'])
>>>c = Coordinate(1,2)
>>>c.x
1
>>>c.y
2deque deque 是为了高效实现插入和删除操作的双向列表,适合用于队列和栈
1
2
3
4
5
6from collections import deque
'a', 'b', 'c']) q = deque([
'x') q.append(
'y') q.appendleft(
q
deque(['y', 'a', 'b', 'c', 'x'])defaultdict 使用 dict 时,如果引用的 Key 不存在,就会抛出 KeyError。如果希望 key 不存在时,返回一个默认值,就可以用 defaultdict:
注意默认值是调用函数返回的,而函数在创建 defaultdict 对象时传入。1
2
3
4
5
6
7from collections import defaultdict
lambda: 'N/A') dd = defaultdict(
'key1'] = 'abc' dd[
'key1'] # key1存在 dd[
'abc'
'key2'] # key2不存在,返回默认值 dd[
'N/A'
除了在 Key 不存在时返回默认值,defaultdict 的其他行为跟 dict 是完全一样的。
OrderedDict 使用 dict 时,Key 是无序的。在对 dict 做迭代时,我们无法确定 Key 的顺序。如果要保持 Key 的顺序,可以用 OrderedDict:
1
2
3
4
5
6
7from collections import OrderedDict
dict([('a', 1), ('b', 2), ('c', 3)]) d =
# dict的Key是无序的 d
{'a': 1, 'c': 3, 'b': 2}
'a', 1), ('b', 2), ('c', 3)]) od = OrderedDict([(
# OrderedDict的Key是有序的 od
OrderedDict([('a', 1), ('b', 2), ('c', 3)])Counter Counter 是一个简单的计数器,例如,统计字符出现的个数:
Counter 实际上也是 dict 的一个子类1
2
3
4
5
6
7from collections import Counter
c = Counter()
for ch in 'programming':
1 c[ch] = c[ch] +
...
c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})
base64
Base64 是一种用 64 个字符来表示任意二进制数据的方法。
首先要理解的问题就是为什么要用字符来表是二进制的数据。维基百科的解释如下: >A binary-to-text encoding is encoding of data in plain text. More precisely, it is an encoding of binary data in a sequence of characters. These encodings are necessary for transmission of data when the channel does not allow binary data, such as when one might attach an image file to an e-mail message, to accomplish this, the data is encoded in some way, such that eight-bit data is encoded into seven-bit ASCII characters
大意就是在数据传输时,某些协议或系统只支持字符的传输(如 email),因此如果需要传输二进制的数据,就要将二进制数据转为字符格式。而 Base64 是一种最常见的二进制编码方法。
Base64 的原理很简单,首先,准备一个包含 64 个字符的数组: 如 ['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
然后对二进制数据进行处理,每 3 个字节一组,一共是 3x8=24bit,划为 4 组,每组正好 6 个 bit, 计算 6 个 bit 表示的数字大小(范围在 0~63)之间,然后查上面的表,这样我们得到 4 个数字作为索引,然后查表,获得相应的 4 个字符,就是编码后的字符串。
因此,Base64 编码会把 3 字节的二进制数据编码为 4 个字符的文本数据
如果要编码的二进制数据的字节数不是 3 的倍数,最后会剩下 1 个或 2 个字节怎么办?Base64 用 00 字节在二进制数据末尾补足后,再在编码的末尾加上 1 个或 2 个 = 号,表示补了一个会两个字节,解码的时候,会自动去掉。 例子如下所示:1
2
3
4
5
6
7
8
9import base64 as b64
',') b64.b64encode(
'LA=='
'l,') b64.b64encode(
'bCw='
'll,') b64.b64encode(
'bGws'
>>>b64.b64decode('LA==')
','
由于标准的 Base64 编码后可能出现字符 + 和 /,在 URL 中就不能直接作为参数,所以又有一种 "url safe" 的 base64 编码,其实就是把字符 + 和 / 分别变成 - 和_:1
2
3
4
5
6'i\xb7\x1d\xfb\xef\xff') base64.b64encode(
'abcd++//'
'i\xb7\x1d\xfb\xef\xff') base64.urlsafe_b64encode(
'abcd--__'
'abcd--__') base64.urlsafe_b64decode(
'i\xb7\x1d\xfb\xef\xff'
Base64 适用于小段内容的编码,比如数字证书签名、Cookie 的内容等。
由于 = 字符也可能出现在 Base64 编码中,但 = 用在 URL、Cookie 里面会造成歧义,所以,很多 Base64 编码后会把 = 去掉 , 如下所示:1
2
3
4# 标准Base64:
'abcd' -> 'YWJjZA=='
# 自动去掉=:
'abcd' -> 'YWJjZA'
去掉 = 后怎么解码呢?因为 Base64 是把 3 个字节变为 4 个字节,所以,Base64 编码的长度永远是 4 的倍数,因此,需要加上 = 把 Base64 字符串的长度变为 4 的倍数,就可以正常解码了。如下面的例子1
2
3
4
5
6
7
8
9
10
11'YWJjZA==') base64.b64decode(
'abcd'
'YWJjZA') base64.b64decode(
Traceback (most recent call last):
...
TypeError: Incorrect padding
def safe_b64decode(s):
return base64.b64decode(s+'='*(4-len(s)%4))
...
'YWJjZA') safe_b64decode(
'abcd'
struct
python 中的 struct 模块的主要作用就是对 python 基本类型值与用 python 字符串格式表示的 C 语言中 struct 类型间的转化(This module performs conversions between Python values and C structs represented as Python strings.)。stuct 模块提供了很简单的几个函数,下面写几个例子。
struct 提供用 format specifier 方式对数据进行打包和解包(Packing and Unpacking)。例如:1
2
3
4
5
6
7
8
9
10
11
12import struct
import binascii
values = (1, 'abc', 2.7)
s = struct.Struct('I3sf')
packed_data = s.pack(*values)
unpacked_data = s.unpack(packed_data)
print 'Original values:', values
print 'Format string :', s.format
print 'Uses :', s.size, 'bytes'
print 'Packed Value :', binascii.hexlify(packed_data)
print 'Unpacked Type :', type(unpacked_data), ' Value:', unpacked_data
输出:1
2
3
4
5Original values: (1, 'abc', 2.7)
Format string : I3sf
Uses : 12 bytes
Packed Value : 0100000061626300cdcc2c40
Unpacked Type : <type 'tuple'> Value: (1, 'abc', 2.700000047683716)
代码中,首先定义了一个元组数据,包含 int、string、float 三种数据类型,然后定义了 struct 对象,并制定了 format‘I3sf’,I 表示 int,3s 表示三个字符长度的字符串,f 表示 float。最后通过 struct 的 pack 和 unpack 进行打包和解包。通过输出结果可以发现,value 被 pack 之后,转化为了一段二进制字节串,而 unpack 可以把该字节串再转换回一个元组. 但是值得注意的是对于 float 的精度发生了改变,这是由一些比如操作系统等客观因素所决定的。打包之后的数据所占用的字节数与 C 语言中的 struct 十分相似。
关于 struct 的更多的具体用法可参考 https://docs.python.org/2/library/struct.html http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
XML
操作 XML 有两种方法:DOM 和 SAX。DOM 会把整个 XML 读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。SAX 是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件。
SAX 只允许读 XML,而 DOM 则允许对 XML 文件进行读写操作。在只读的情况下,优先考虑 SAX,因为 DOM 实在太占内存。
除了 python 自带的 xml 包可用于处理 XML 文件,第三发库如 lxml 也可以被用来处理 XML 文件。
python 自带的 xml 包具体使用的实例代码可参考: http://www.tutorialspoint.com/python/python_xml_processing.htm
参考: http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000