吴良超的学习笔记

Python语法杂记

本文主要记录一些学习过程中遇到的一些比较零碎的python语法知识。

常见易错用法

for循环中修改下标的值

python中的for循环一般会写成这样

1
2
for i in range(10):
....

上面的语句中循环了10此,i的值从0增到9。在Java中可以在for循环中修改i的值,从而跳过一些i的值不处理,但是在上面的语法中无效,因为range实际上生成了一个0到9的list,每次i会取其中的一个值,所以如果没有break的话,i会取遍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组成的一个list
  • dict.values()返回字典dict所有values组成的一个list
  • dict.items()返回字典dict所有kv组成的一个list,kv以tuple的形式存储

对字典排序

通过sorted函数可以根据字典的key或value对字典排序,并返回一个元素类型为tuple为的list,每个tuple代表字典中的一个元素。排序不会改变原来字典中的值。

1
2
3
4
5
6
7
8
a = {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
3
s={} # 非法
s={1,2,3,4} # 合法
s=set() # 也可用s = set(list),用一个集合提取list中的不重复元素

不可变集合

对应于元组(tuple)与列表(list)的关系,对于集合(set),Python提供了一种叫做不可变集合(frozen set)的数据结构。

创建一个不可变集合

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&s2s1.intersection(s2),返回集合s1和集合s2的交集
  • 并集,s1|s2s1.union(s2),返回集合s1和集合s2的并集
  • 差集,s1-s2s1.defference(s2)返回s1中有但s2中没有的元素的集合
  • 对称差集,s1^s2s1.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
2
a = [1,2,3,4,5,6]
print sum([i for i in a if i%2==0])

sum()是求一个列表内所有元素的和的内置函数,传入的参可以为一个列表,而[i for i in a if i%2==0]则是列表推导式,该语句生成了列表[2,4,6]

但是,Python会生成这个列表,然后再将它放到垃圾回收机制中(因为没有变量指向它),这毫无疑问是种浪费。

为了解决这种问题,与rang()xrange()的问题类似,Python使用生成器(generator)表达式来解决这个问题:

将sum中代表list的括号去掉即可,修改后如下所示:

1
2
a = [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
8
def 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
5
def 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
6
def 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
9
>>> a,b,c=[1,2,3]
>>> print a,b,c
1 2 3
>>> a,b,c=(1,2,3)
>>> print a,b,c
1 2 3
>>> a,b,c="123"
>>> 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
5
a = 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()函数的功能
>>> reduce((lambda x,y:x+y),[1,2,3,4,5])
15

上面均利用了lambda函数,也可以将lambda函数改成def定义好的函数。

filter函数

Python内建的filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。和map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

例子:过滤掉1~100中的素数并返回结果

1
2
3
4
5
6
7
import 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
7
def 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
7
def lazy_sum(*args):
def sum():
s = 0
for i in args:
s+=i
return s
return sum

调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

1
2
3
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> 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
9
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

你可能认为调用f1( ),f2( )和f3( )结果应该是1,4,9,但实际结果是:

1
2
>>>print  f1(),f2(),f3()
9 9 9

全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变.如下面的例子

1
2
3
4
5
6
def 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
11
def log(func):
def wrapper():
print 'before %s' %func.__name__
return func()
return wrapper

@log
def hello():
print 'hello'

hello()

输出结果为:

1
2
before hello_world
hello

实际上执行hello()时相当于执行了hello=log(hello),即将hello指向了返回的wrapper函数,而这也带来了一个问题,就是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
14
import functools
def log(func):
@functools.wraps(func)
def wrapper():
print 'before %s' %func.__name__
return func()
return wrapper

@log
def hello_world():
print 'hello'

hello_world()
print hello_world.__name__

这时打印出来的结果是

1
2
3
before hello_world
hello
hello_world

上面的例子中装饰器均没有参数,下面给出装饰器带有参数的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def log_a(text):
def decorator_a(func):
@functools.wraps(func)
def wrapper_a():
print 'args in the decorator is %s'%text
func()
return wrapper_a
return decorator_a

@log_a('haha')
def hello_world_a():
print 'hello'

hello_world_a()
print hello_world_a.__name__

输出结果如下:

1
2
3
args in the decorator is haha
hello
hello_world_a

执行hello_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
4
>>> len('ABC')
3
>>> 'ABC'.__len__()
3

如果试图获取不存在的属性,会抛出AttributeError的错误

动态绑定属性和方法

定义了一个class,或者创建了一个class的实例后,我们可以给该类或实例绑定任何属性和方法,这就是动态语言的灵活性。如下面的例子
先定义一个类

1
2
3
>>> class Student(object):
... pass
...

然后,尝试给实例绑定一个属性:

1
2
3
4
>>> s = Student()
>>> s.name = 'hello' # 动态给实例绑定一个属性
>>> print s.name
hello

如果要限定能够绑定的属性,可以在原来的类中添加__slots__变量,变量的内容设为能够动态绑定的属性即可。

隐藏getter和setter为类的属性

通过装饰器@property可以隐藏类对某个属性的get方法和set方法,见下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
@property
def birth(self):
return self._birth

@birth.setter
def birth(self, value):
self._birth = value

@property
def age(self):
return 2014 - self._birth

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@birth.setter,这个装饰器负责把一个setter方法变成属性赋值,并且在这个方法内可以限制复制的的范围等。

调用方法如下所示

1
2
3
4
>>>s = Student()
>>>s.birth = 2001
>>>print s.birth,s.age
2001 13

上面的s.birth = 2001 实际上是执行了装饰器@birth.setter装饰的birth方法,因此可在这个方法内加上赋值的限制条件,过滤不合法的赋值。

也可以将一个属性定义为只读属性,只定义getter方法即可。如上面的age方法。

类的一些内部函数

__str__

该函数是在直接打印对象时输出的内容,如下例子所示

1
2
3
4
5
class Student(object):
pass

s = Student()
print s

输出内容为<__main__.Student object at 0x02124DF0>,表示对象在内存中的地址,可以重写这个函数的输出,见下面的例子

1
2
3
4
5
6
class Student(object):
def __str__(self):
return 'object student'

s = Student()
print s

再次执行的时候会输出object 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
12
class 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 # 返回下一个值

把Fib实例作用于for循环:

1
2
3
4
5
6
7
8
9
10
11
>>> for n in Fib():
... print n
...
1
1
2
3
5
...
46368
75025

__getitem__

Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:

1
2
3
4
>>> Fib()[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing

要表现得像list那样按照下标取出元素,需要实现getitem()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class 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()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3

__call__

一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本身上调用呢?类似instance()?在Python中,答案是肯定的。

任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。请看示例:

1
2
3
4
5
6
class Student(object):
def __init__(self, name):
self.name = name

def __call__(self):
print('My name is %s.' % self.name)

调用方式如下:

1
2
3
>>> s = Student('Michael')
>>> s()
My name is Michael.

__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样

判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有call()的类实例:

1
2
3
4
5
6
7
8
9
10
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('string')
False

通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

错误、调试和测试

概念

常用的调试结构

1
2
3
4
5
6
7
try
....
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
8
class 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
15
def 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
4
try:
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
6
import 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
5
INFO: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.pyn命令执行当前代码并转到下一行,p 变量名打印出具体的变量,q命令退出调试程序。

除了上面的使用方法,还可以在可能出错的地方放一个pdb.set_trace(),相当于设置一个断点。运行代码时,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行

多进程和多线程

多进程

python提供的跨平台多进程模块为multiprocessing, 使用的方式如下:

1
2
3
4
5
6
7
8
from 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
14
import 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()

当参数多了的时候,这样一层层传下去就会显得比较麻烦。因此引入了ThreadLocal的概念,可将上面的代码改写成如下的样式实现相同的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 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
    2
  • deque
    deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈

    1
    2
    3
    4
    5
    6
    >>> from collections import deque
    >>> q = deque(['a', 'b', 'c'])
    >>> q.append('x')
    >>> q.appendleft('y')
    >>> q
    deque(['y', 'a', 'b', 'c', 'x'])
  • defaultdict
    使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:

    1
    2
    3
    4
    5
    6
    7
    >>> from collections import defaultdict
    >>> dd = defaultdict(lambda: 'N/A')
    >>> dd['key1'] = 'abc'
    >>> dd['key1'] # key1存在
    'abc'
    >>> dd['key2'] # key2不存在,返回默认值
    'N/A'

注意默认值是调用函数返回的,而函数在创建defaultdict对象时传入

除了在Key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的。

  • OrderedDict
    使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict:

    1
    2
    3
    4
    5
    6
    7
    >>> from collections import OrderedDict
    >>> d = dict([('a', 1), ('b', 2), ('c', 3)])
    >>> d # dict的Key是无序的
    {'a': 1, 'c': 3, 'b': 2}
    >>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
    >>> od # OrderedDict的Key是有序的
    OrderedDict([('a', 1), ('b', 2), ('c', 3)])
  • Counter
    Counter是一个简单的计数器,例如,统计字符出现的个数:

    1
    2
    3
    4
    5
    6
    7
    >>> from collections import Counter
    >>> c = Counter()
    >>> for ch in 'programming':
    ... c[ch] = c[ch] + 1
    ...
    >>> c
    Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})

Counter实际上也是dict的一个子类

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用\x00字节在二进制数据末尾补足后,再在编码的末尾加上1个或2个=号,表示补了一个会两个字节,解码的时候,会自动去掉。 例子如下所示:

1
2
3
4
5
6
7
8
9
>>> import base64 as b64
>>> b64.b64encode(',')
'LA=='
>>> b64.b64encode('l,')
'bCw='
>>> b64.b64encode('ll,')
'bGws'
>>>b64.b64decode('LA==')
','

由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种”url safe”的base64编码,其实就是把字符+和/分别变成-和_

1
2
3
4
5
6
>>> base64.b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd++//'
>>> base64.urlsafe_b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd--__'
>>> base64.urlsafe_b64decode('abcd--__')
'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
>>> base64.b64decode('YWJjZA==')
'abcd'
>>> base64.b64decode('YWJjZA')
Traceback (most recent call last):
...
TypeError: Incorrect padding
>>> def safe_b64decode(s):
... return base64.b64decode(s+'='*(4-len(s)%4))
...
>>> safe_b64decode('YWJjZA')
'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
12
import 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
5
Original 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