Python随手记(持续)

语法

1.字符串多行表示:'''

1
2
3
4
5
print('''
hello
world
!
''')

这个特性类似于js的\``。

2.布尔值:True/False

分别对应js的true/false

3.与/或/非:and/or/not

分别对应js的&&/||/not

4.数据类型(基本)

数字(整数/浮点数)、字符串、布尔值、空值None

5.除法

/

/除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:

1
2
10 / 3
/* 3.33333333 */
1
2
9 / 3
/* 3.0 */

//

//称为地板除,两个整数的除法仍然是整数:

1
2
10 // 3
/* 3 */

6.字符串编码操作:ord()/chr()

1
2
ord('A')
/* 65 */
1
2
chr(65)
/* 'A' */

对应js的String.prototype.charCodeAt(0)/String.fromCharCode()

对bytes类型的数据用带b前缀的单引号或双引号表示:

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。如:

1
b'ABC'

‘ABC’和b’ABC’不同,前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。

以Unicode表示的str通过encode()方法可以编码为指定的bytes。

1
2
'ABC'.encode('ascii')
/* b'ABC' */
1
2
'中文'.encode('utf-8')
/* b'\xe4\xb8\xad\xe6\x96\x87' */
1
2
'中文'.encode('ascii')
/*UnicodeEncodeError:...*/

如果bytes中只有一小部分无效的字节,可以传入errors=’ignore’忽略错误的字节:

1
2
b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
/*'中'*/

len()

要计算str包含多少个字符,可以用len()函数:

1
2
len('Hello')
/* 5 */

如果换成bytes,len()函数就计算字节数:

1
2
len(b'\xe4\xb8\xad\xe6\x96\x87')
/* 6 */

1
2
len('中文'.encode('utf-8'))
/* 2 */

7.字符串格式化

和C语言是一致的,用%实现

1
2
'Hello, %s' % 'world'
/* 'Hello, world' */

1
2
'Hi, %s, you have $%d.' % ('Michael', 1000000)
/* 'Hi, Michael, you have $1000000.' */
占位符 替换内容
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数

其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数

1
2
'%2d-%02d' % (3, 1)
/*3-01*/

1
2
'%.2f' % 3.1415926
/* 3.14 */

如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串

1
2
'Age: %s. Gender: %s' % (25, True)
/* 'Age: 25. Gender: True' */

有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%来表示一个`%

1
2
'growth rate: %d %%' % 7
/* 'growth rate: 7 %' */

format()

使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}{1}……,不过这种方式写起来比%要麻烦得多:

1
2
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
/* 'Hello, 小明, 成绩提升了 17.1%' */

8.List/Tuple

List

类似于js的数组,list是一种有序的集合,可以随时添加和删除其中的元素。

1
['Michael', 'Bob', 'Tracy']

获取list长度:len()

取值:[索引值],如

1
2
3
names = ['Michael', 'Bob', 'Tracy']
names[0]
/* 'Michael' */

但与js不同的是

  • 1.超出范围会报错,如:

    1
    2
    name[3]
    /* IndexError:... */
  • 2.负值索引为倒数,如:

    1
    2
    name[-1]
    /* 'Tracy' */
操作方法
  • append():末尾添加元素,类似于js的push(),如

    1
    2
    names.append('Jack')
    /* ['Michael', 'Bob', 'Tracy', 'Jack'] */
  • pop():末尾删除元素,类似于js的pop(),如

    1
    2
    names.pop();
    /* ['Michael', 'Bob'] */
  • pop(index):删除指定索引元素,类似于js的splice(index, 1),如

    1
    2
    names.pop(1)
    /* ['Michael', 'Tracy'] */
  • insert(index, value):在指定索引添加元素,类似于js的splice(index - 1, 0, value),如

    1
    2
    names.insert(1, 'Tom')
    /* ['Michael', 'Tom', 'Bob', 'Tracy'] */

Tuple

tuple和list非常类似,但是tuple一旦初始化就不能修改,没有append(),insert()这样的方法。
不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。如

1
t = (1, 2)

但要注意元组元素只有一个的时候,像刚才这样定义会有问题

1
2
t = (1)
/* 1 */

这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。

所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

1
t = (1,)

元组中的可变性

如果元组中某一个元素是list,那么该元素的元素可变,

1
2
t = ('a', 'b', ['A', 'B'])
t[2][0] = 'C';

Dict

dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。类似js的Object,不过不一样的是如果取值事key不存在,dict就会报错。

1
2
3
4
5
d = {
'Michael': 95,
'Bob': 75,
'Tracy': 85
}

要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:

1
2
'Jack' in d
/* False */

二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:

1
2
3
4
5
6
7
8
d.get('Jack')
/* None,无返回 */

d.get('Jack', 123)
/* 123 */

d.get('Michael', 123)
/* 95 */

要删除一个key,用pop(key)方法
1
d.pop('Jack');

Set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。类似js的Set

1
s = set([1, 2, 3])
  • add(key):添加key
  • remove(key):删除key

9.条件判断

if elif else:分别对应js的if () {} else if () {} else {}

1
2
3
4
5
6
7
8
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>

10.循环

break语句可以提前退出循环,continue语句跳过当前的这次循环。

for in

类似js的for in

1
2
3
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

range(num)转为有序序列,并支持循环

1
2
3
4
5
6
7
8
9
10
11
for val in range(6):
print(val)

/*
0
1
2
3
4
5
*/

可以通过list()方法转为list,如:

1
2
list(range(6))
/* [0,1,2,3,4,5] */

while

类似于js的while

1
2
3
4
5
6
sum = 0
n = 99
while n > 0:
sum = sum + n
n = n - 2
print(sum)

10.数学计算

  • abs():类似js的Math.abs()
  • max():类似js的Math.max(),不过可以接收任意多个参数。如max(1,2,3,4,5)

11.类型转换

  • int():转为整数
  • float():转为浮点数
  • str():转为字符串
  • bool():转为布尔值

12.定义函数

定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

如:

1
2
3
4
5
def my_abs(x):
if x >= 0:
return x
else:
return -x

空函数

def nop():
pass

pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

pass还可以用在其他语句里。

1
2
3
4
if a > 10:
pass
else:
pass

返回多个值

1
2
3
4
5
6
7
8
import math

def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny

x, y = move(100, 100, 60, math.pi / 6)

即返回值是一个tuple。

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

1
2
3
4
5
6
7
8
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

person('Michael', 30)
/* 'name: Michael age: 30 other: {}' */

person('Adam', 45, gender='M', job='Engineer')
/* 'name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}' */

命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。

仍以person()函数为例,我们希望检查是否有city和job参数:

1
2
3
4
5
6
7
8
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)

调用

1
person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:

1
2
def person(name, age, *, city, job):
print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。

调用方式如下:

1
2
person('Wayne', 24, city='Hangzhou', job='Engineer')
/* Wayne 24 Hangzhou Engineer */

命名关键字参数可以有缺省值,从而简化调用:

1
2
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)

调用

1
2
person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

比如定义一个函数,包含上述若干种参数:

1
2
3
4
5
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

递归函数

简单,举一个阶乘例子就好:

1
2
3
4
def fact(n):
if n==1:
return 1
return n * fact(n - 1)

与js一样,python也有调用栈溢出问题,解决的一种方案是通过尾递归优化

13.高级用法

切片

我们先创建一个0-99的数列:

1
2
L = list(range(100))
/* [0, 1, 2, 3, ..., 99] */

可以通过切片轻松取出某一段数列。比如前10个数:

1
2
L[:10]
/* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] */

后10个数

1
2
L[-10:]
/* [90, 91, 92, 93, 94, 95, 96, 97, 98, 99] */

前11-20个数:

1
2
L[10:20]
/* [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] */

前10个数,每两个取一个:

1
2
L[:10:2]
/* [0, 2, 4, 6, 8] */

所有数,每5个取一个:

1
2
L[::5]
/* [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95] */

甚至什么都不写,只写[:]就可以原样复制一个list:

1
2
L[:]
/* [0, 1, 2, 3, ..., 99] */

tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:

1
2
(0, 1, 2, 3, 4, 5)[:3]
/* (0, 1, 2) */

字符串’xxx’也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:

1
2
3
4
5
'ABCDEFG'[:3]
/* 'ABC' */

'ABCDEFG'[::2]
/* 'ACEG' */

总得来说,比js的slice灵活。。。

迭代

Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。

list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:

1
2
3
4
5
6
7
8
9
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
print(key)

/*
a
c
b
*/

默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()

所以,当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。

那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断

1
2
3
4
5
6
7
8
9
from collections import Iterable
isinstance('abc', Iterable) # str是否可迭代
/* True */

isinstance([1,2,3], Iterable) # list是否可迭代
/* True */

isinstance(123, Iterable) # 整数是否可迭代
/* False */

如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

1
2
3
4
5
6
7
8
for i, value in enumerate(['A', 'B', 'C']):
print(i, value)

/*
0 A
1 B
2 C
*/
1
2
3
4
5
6
7
8
for x, y in [(1, 1), (2, 4), (3, 9)]:
print(x, y)

/*
1 1
2 4
3 9
*/

列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

比如要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11)):

1
2
list(range(1, 11))
/* [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] */

但如果要生成[1x1, 2x2, 3x3, …, 10x10]怎么做?方法一是循环:

1
2
3
4
5
L = []
for x in range(1, 11):
L.append(x * x)

/* [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] */

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:

1
2
[x * x for x in range(1, 11)]
/* [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] */

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

1
2
[x * x for x in range(1, 11) if x % 2 == 0]
/* [4, 16, 36, 64, 100] */

还可以使用两层循环,可以生成全排列:

1
2
[m + n for m in 'ABC' for n in 'XYZ']
/* ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ'] */

三层和三层以上的循环就很少用到了。

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

1
2
3
import os # 导入os模块,模块的概念后面讲到
[d for d in os.listdir('.')] # os.listdir可以列出文件和目录
/* ['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode'] */

for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:

1
2
3
4
5
6
7
8
9
d = {'x': 'A', 'y': 'B', 'z': 'C' }
for k, v in d.items():
print(k, '=', v)

/*
y = B
x = A
z = C
*/

列表生成式也可以使用两个变量来生成list:

1
2
3
4
d = {'x': 'A', 'y': 'B', 'z': 'C' }
[k + '=' + v for k, v in d.items()]

/* ['y=B', 'x=A', 'z=C'] */

最后把一个list中所有的字符串变成小写:

1
2
3
L = ['Hello', 'World', 'IBM', 'Apple']
[s.lower() for s in L]
/* ['hello', 'world', 'ibm', 'apple'] */

在一个列表生成式中,for前面的if … else是表达式,而for后面的if是过滤条件,不能带else。

生成器

有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

1
2
3
4
5
L = [x * x for x in range(10)]
/* [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] */

g = (x * x for x in range(10))
/* <generator object <genexpr> at 0x1022ef630> */

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

可以通过next()函数获得generator的下一个返回值:

1
2
3
4
5
6
7
8
9
10
next(g)
/* 0 */
/* next... */
/* 81 */
next(g)
/*
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
*/

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

正确的方法是使用for循环,因为generator也是可迭代对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
g = (x * x for x in range(10))
for n in g:
print(n)
/*
0
1
4
9
16
25
36
49
64
81
*/

如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

1
2
3
4
5
6
7
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

迭代器

可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如list、tuple、dict、set、str等;
  • 一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

可以使用isinstance()判断一个对象是否是Iterable对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections.abc import Iterable
isinstance([], Iterable)
/* True */

isinstance({}, Iterable)
/* True */

isinstance('abc', Iterable)
/* True */

isinstance((x for x in range(10)), Iterable)
/* True */

isinstance(100, Iterable)
/* False */

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

可以使用isinstance()判断一个对象是否是Iterator对象:

1
2
3
4
5
6
7
8
9
10
11
12
from collections.abc import Iterator
isinstance((x for x in range(10)), Iterator)
/* True */

isinstance([], Iterator)
/* False */

isinstance({}, Iterator)
/* False */

isinstance('abc', Iterator)
/* False */

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

1
2
3
4
5
isinstance(iter([]), Iterator)
/* True */

isinstance(iter('abc'), Iterator)
/* True */

为什么list、dict、str等数据类型不是Iterator?

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

14.函数式编程

高阶函数

“函数是一等公民”,函数也可以作为参数。

python内置了map()和reduce()函数。

map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

如:

1
2
3
4
5
6
def f(x):
return x * x

r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)
/* [1, 4, 9, 16, 25, 36, 49, 64, 81] */

map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。

map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数。

reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方说对一个序列求和,就可以用reduce实现:

1
2
3
4
5
6
from functools import reduce
def add(x, y):
return x + y

reduce(add, [1, 3, 5, 7, 9])
/* 25 */

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

1
2
3
4
def is_odd(n):
return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))

内置的sorted()函数就可以对list进行排序:

1
2
sorted([36, 5, -12, 9, -21])
/* [-21, -12, 5, 9, 36] */

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

1
2
sorted([36, 5, -12, 9, -21], key=abs)
/* [5, 9, -12, -21, 36] */

返回函数

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。如:

1
2
3
4
5
6
7
8
9
10
11
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

f = lazy_sum(1, 3, 5, 7, 9)
f();
/* 25 */

闭包:注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

匿名函数

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

1
2
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
/* [1, 4, 9, 16, 25, 36, 49, 64, 81] */

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

1
2
3
f = lambda x: x * x
f(5);
/* 25 */

装饰器

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

1
2
3
4
5
6
def now():
print('2015-3-25')

f = now
f()
/* 2015-3-25*/

函数对象有一个__name__属性,可以拿到函数的名字:

1
2
3
4
now.__name__
/* 'now' */
f.__name__
/* 'now' */

现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

1
2
3
@log
def now():
print('2015-3-25')

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

1
2
3
now()
call now():
/* 2015-3-25 */

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

1
2
def int2(x, base=2):
return int(x, base)

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

1
2
3
4
import functools
int2 = functools.partial(int, base=2)
int2('1000000')
/* 64 */

所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。