python_logo

1. 函数

.1 函数调用

Python中内置了许多函数,在python程序中我们可以直接通过函数名调用,例如: abs,chr,ord等等。我们也可以从Python的官方文档中查看到内置函数的具体信息:https://docs.python.org/2/library/functions.html#abs

Python中函数的函数名其实本质上是一个指向函数对象的引用,完全可以把函数名赋值给一个变量,这相当于给函数换了个“别名”,例如:

>>> a = abs
>>> a(-1)
1
>>> a(100)
100

在使用Python中内置的函数时,需要根据函数的参数定义,传入合适的参数。

1.2 函数定义

在Python中,定义一个函数需要使用def语句,依次写出函数名、括号、括号中的参数和冒号“:“,然后在缩进块中编写函数体,函数的返回值用return语句返回,下面就以自定义的求绝对值函数my_abs为例:

def my_abs(x):
    if x >= 0:
    	return x
    else:
    	return -x

1.3 空函数

如果想定义一个什么也不做的空函数,可以采用pass语句:

def nop():
	pass

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

1.4 参数检查

Python解释器可以检查我们调用自定义函数时的参数的个数是否正确,但它不会检查我们调用函数时函数参数的类型是否正确,所以我们在自定义函数时,最好还要添加上参数类型检查。

在Python的内置函数中有一个函数叫做isinstance(),这个函数是用来判断一个变量是否是某种类型数据的实例,例如我们将上面的my_abs函数进行该进,为其添加上参数检查功能:

def my_abs(x):
    if not isinstance(x,(int,float)):
    	raise TypeError('bad operand type')
    if x >= 0:
    	return x
    else:
    	return -x

1.5 返回多个返回值

在Python中支持返回多个返回值,看起来这与我们以前C语言中一个函数只能返回一个返回值有很大的不同啊?但是,实际上呢,Python中函数返回多个函数值,其实本质上是将多个返回值,组成一个tuple类型的数据返回,所以,本质上函数的返回值还是一个。 例如:

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)
print x,y

r = move(100,100,60,math.pi/6)
print r

最终运行得到的结果是:

151.961524227 130.0 (151.96152422706632, 130.0)

1.6 函数的参数

Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

默认参数

下面就以X的N次幂为例,介绍函数默认参数的用法:

def power(x,n=2):
	s = 1
    while n>0:
    	s = s*x
        n = n -1
    return s
    
print 'power(5) = ',power(5)
print '\npower(5,2) = ',power(5,2)

当我们调用power(5)时,就相当调用了power(5,2),最后运行的结果为:

power(5) = 25

power(5,2) = 25

使用默认参数时的注意事项: 第一,必选参数在前,默认参数在后,否则Python解释器会报错。 第二,当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。 第三,默认参数一定要指向不可变对象。

可变参数

可变参数,顾名思义就是函数中参数的个数是可变的,不固定的,可以是0个,1个,2个等等。

*可变参数的定义方法是:只要在函数的参数列表前加上一个星号即可,例如: **

def calc(*numbers):
	sum = 0
    for x in numbers:
    	sum += x*x
    return sum
    
print 'calc(1,2) = ',calc(1,2)
print 'calc() = ',calc()

最后运行得到的结果是:

calc(1,2) = 5 calc() = 0

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。关键字参数的定义: 在函数的形参前加上两个星号”**“,请看示例:

def person(name,age,**kw):
    print 'name:',name,'age:',age,'others:',kw

函数person在被调用时可以只传入必选参数,也可以传入任意个数的关键字参数。

def person(name,age,**kw):
    print 'name:',name,'age:',age,'others:',kw

print person('Bob',15)
print person('Adam',35,job='engineer',gender='m')
print person('Peter',20,city='ShangHai')

运行的结果是:

name: Bob age: 15 others: {} None name: Adam age: 35 others: {‘gender’: ‘m’, ‘job’: ‘engineer’} None name: Peter age: 20 others: {‘city’: ‘ShangHai’} None

在上面的运行结果中,含有空值“None”的输出的原因是函数person的默认返回值为None。

关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

参数的组合

在Python中定义函数,可以有必选参数、默认参数、可变参数和关键字参数,这4中参数可以一起使用,也可以使用其中的某一些,但是它们在函数参数列表中的定义的先后顺序必须是: 必选参数、默认参数、可变参数、关键字参数。

比如,定义一个函数包含上面的四种参数:

def func(a,b,c=0,*args,**kw)
	print 'a = ',a,'b = ',b,'c = ',c,'args = ',args,'kw = ',kw

在函数调用时,Python解释器按照参数名和参数位置将参数传递进去

def func(a,b,c=0,*args,**kw):
	print 'a=:',a,'b=:',b,'c=',c,'args=:',args,'kw=:',kw

func(1,2)

func(1,2,c=3)

func(1,2,3,'a','b')

func(1,2,3,'a',5)

func(1,2,3,'a','b',x=99)

最后运行得到的结果是:

a=: 1 b=: 2 c= 0 args=: () kw=: {} a=: 1 b=: 2 c= 3 args=: () kw=: {} a=: 1 b=: 2 c= 3 args=: (‘a’, ‘b’) kw=: {} a=: 1 b=: 2 c= 3 args=: (‘a’, 5) kw=: {} a=: 1 b=: 2 c= 3 args=: (‘a’, ‘b’) kw=: {‘x’: 99}

对于任意的函数都可以通过func(*args,**kw)的方式进行调用,无论它的函数参数定义是什么样的,例如:

args = (1,2,3,4)
kw = {'x':99}
func(*args,**kw)

最后运行得到的结果是:

a=: 1 b=: 2 c= 3 args=: (4,) kw=: {‘x’: 99}

最后,特别要注意的是 *args是可变参数,接受的是一个tuple **kw 是关键字参数,接受的是一个dict

可变参数既可以直接传入: func(1, 2, 3),又可以先组装list或tuple,再通过*args传入: func(*(1, 2, 3));

关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入: func(**{‘a’: 1, ‘b’: 2})。

使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

2. 高级特性

Python的设计思想就是简单,能够用一行代码解决的问题,绝对不用5行代码取解决,所以Python中就有许多非常简洁又非常方便地一些高级特性,下面就一一作个介绍。

2.1 切片

Python中可以通过切片操作非常方便地取出list或者tuple中的想要的元素,而不必很繁琐地通过索引和循环的方式来获取元素。

例如,我们取一个list中前3个元素,我们就可以用以下的一行语句来解决:

>>> L = ['Michael','Bob','James','Peter']
>>> L[0:3]

L[0:3]表示从索引0开始取,直到索引3时结束,但是不包括索引3,所以最后取得元素是L[0],L[1],L[2]共三个元素。

其中L[0:3]也可以简写为L[:3]。

和取List的倒数索引类似,Python也支持倒数的切片方式,例如:

>>> L[-2:-1]
['James']
>>> L[-3:-1]
['Bob', 'James']

Python还支持每隔几个元素的切片操作,例如:

>>> L
['Michael', 'Bob', 'James', 'Peter', 'Fred', 'Tom']
>>> L[::2]
['Michael', 'James', 'Fred']
>>> L[0:6:3]
['Michael', 'Peter']

其中L[0:6:3]表示的意思是从索引0开始取,每隔3个元素取一个元素,直到取到索引位置为6为止,但是不包括索引位置6。

对字符串同样可以采用切片操作,例如:

>>> str = 'Hello World!'
>>> str
'Hello World!'
>>> str[:6]
'Hello '

2.2 迭代

Python中的迭代是指: 能够通过for循环来遍历可迭代的对象,在Python中可以迭代的对象有list,tuple,dict,字符串等。

例如,dict类型对象的迭代方法如下所示:

d = {'a':1,'b':2,'c':3}

print d

for key in d:
    print key

最后运行得到的结果是:

{‘a’: 1, ‘c’: 3, ‘b’: 2} a c b

因为dict的存储不是按照list的方式进行排列的,所以输出的结果可能不一样。

判断一个对象是不是可迭代的对象的方法是: 通过collections模块的iterable类型判断,例如:

>>> from collections import Iterable
>>> isinstance('abc',Iterable)
True
>>> isinstance([1,2,3],Iterable)
True
>>> isinstance(123,Iterable)
False

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

for i,value in enumerate(['A','B','C']):
	print i,value

最后运行得到的结果是:

0 A 1 B 2 C

2.3 列表生成式

列表生成式即List Comprehensions,它是Python内置的非常简单却十分强大的List生成式。

按照前面所学习的知识,在生成List对象时,我们可能会直接初始化赋值给出List中的数据元素,也可能通过range函数来构造List,或者通过for循环和List的append函数来构造list,但是上面这些方法,都有些繁琐,在Python中就是怎么简单就怎么来,下面我们就通过列表生成式的例子来介绍列表生成式:

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

上面这个例子就给我们演示了生成1-10数字的x*x的方法,是不是很简单呢?

写列表生成式时,首先要把生成的元素放在前面,后面再根for循环,就能创建出list了

而且我们也可以通过列表生成式,开生成全排列,例如:

>>> [m+n for m in 'ABC' for n in '123']
['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']

列出当前目录下的所有文件和目录,我可以通过下面这一行简单的代码就能实现了:

>>> import os
>>> [d for d in os.listdir('.')]
['define_generator.pyc', 'iteration_example.py~', 'iteration_example.py', 'fibonacci.py', 'define_generator.py', 'fibonacci.pyc']

2.4 生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

创建generator的方法有很多,第一种方法就是把生成式中的[]换成()就可以了,由此就能够生成一个generator,例如:

>>> L = [x*x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x*x for x in range(10))
>>> g
<generator object <genexpr> at 0x7f425f70ecd0>

list可以通过索引或者迭代的方式进行访问,那么如何访问generator中的元素呢? 前面讲到,generator的目的就是为了节约内存空间,它的元素不是全部一次性就计算出来的,而是通过保存了算法,然后根据算法一步一步地计算出来的。因此,generator中采用了next()方法来获取generator中的元素,例如:

>>> g.next()
0
>>> g.next()
1
>>> g.next()
4
>>> g.next()
9
>>> g.next()
16
>>> g.next()
25
>>> g.next()
36
>>> g.next()
49
>>> g.next()
64
>>> g.next()
81
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

当generator计算到最后一个元素之后,如果还继续调用next()方法,generator就会抛出一个StopIteration的错误。

但是上面这种调用generator的方法的确有些复杂,由于generator对象也是一个可迭代的对象,所以我们可以通过for循环来访问generator中的元素,例如:

>>> g = (x*x for x in range(10))
>>> for n in g:
...     print n
...
0
1
4
9
16
25
36
49
64
81

创建generator的第二种方法就是: 如果一个函数定义中包含了yield关键字,那么这个函数就不在是一个普通函数,而是一个generator。

在这里最难理解的就是generator的执行顺序和函数的执行顺序不一样。函数是顺序执行,遇到return语句或者执行完函数的最后一行语句后返回。而变成generator的函数,在每次遇到next()的时候执行,遇到yield语句返回,再次执行时从上一次返回的yield语句处接下去继续执行,直到遇到函数体中的return语句或者执行到函数体的最后一行语句才停止,下面就举个简单的例子:

>>> def odd():
...     print 'step 1'
...     yield 1
...     print 'step 2'
...     yield 2
...     print 'step 3'
...     yield 3
... 
>>> o = odd()
>>> o.next()
step 1
1
>>> o.next()
step 2
2
>>> o.next()
step 3
3
>>> o.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

3. 函数式编程

3.1 高阶函数

变量可以指向函数,函数的参数又可以接受变量,所以当一个函数A可以接受另一个函数B作为参数时,我们就称函数A为高阶函数。例如,

>>> def add(x,y,f):
...     return f(x)+f(y)
... 
>>> print add(-10,10,abs)
20

上面的例子中,函数add就是一个高阶函数,它能够接受一个函数作为参数。

把函数当做参数传入到另外一个函数中,这样的函数就称为高阶函数。

map/reduce高阶函数的用法

Python中内建了map()和reduce()函数,有关Map/Reduce的概念,请详见google的一片论文,MapReduce: Simplified Data Processing on Large Clusters

map

map函数接收两个参数,一个是函数,一个是序列,map将接收到的函数依次作用于序列中的每个元素,并把结果作为新的list返回,例如:

>>> def f(x):
...     return x*x
...     
... 
>>> map(f,[1,2,3,4,5])
[1, 4, 9, 16, 25]

所以,map作为高阶函数,它把计算规则抽象化了,因此,我们不仅可以计算简单的f(x)=x*x,而且还可以计算更为复杂的函数。

reduce

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

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

例如,一个序列求和就可以通过reduce实现

>>> def add(x,y):
...     return x+y
... 
>>> reduce(add,[1,2,3,4,5])
15

在介绍完,map和reduce的用法之后,我们就给出一个两者混合使用的例子: 将字符串形式表示的整数转换成整数。

>>> def fn(x,y):
...     return x*10+y
... 
>>> def num2int(s):
...     return {'0':0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
... 
>>> reduce(fn,map(num2int,'13579'))
13579
练习

利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入: [‘adam’, ‘LISA’, ‘barT’],输出: [‘Adam’, ‘Lisa’, ‘Bart’]。

解答:

def name_format(s):
	s = s.lower()
	s = s.capitalize()
	return s

print map(name_format,['adam','LISA','barT'])

filter高阶函数的用法

Python内建的filter函数用于过滤序列。和map()相似,filter()也接受一个函数和一个序列共两个参数,但和map()不同的是,filter()依次把函数作用于序列中每个元素,然后根据返回值是True还是False来保留还是丢弃该元素。

例如,在一个list中删除掉其中的偶数,那么这就可以这么写:

def is_odd(x):
	return x%2 == 1

print filter(is_odd,[0,1,2,3,4,5,6,7,8,9])

最后运行得到的结果是:

[1, 3, 5, 7, 9]

如果is_odd()得到的结果是True,那么则保留该元素,如果is_odd()得到的函数是False,那么则删除该元素。

练习

请尝试使用filter()删除1-100之间的素数。

import math

def filter_prime_number(x):
	i = 2
	n = (int)(math.sqrt(x))
	while i <= n:
		if x % i == 0:
			return True
		i = i + 1
	if i > n:
		return False

print filter(filter_prime_number,range(1,101))

运行得到的结果是:

[4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45, 46, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 60, 62, 63, 64, 65, 66, 68, 69, 70, 72, 74, 75, 76, 77, 78, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 98, 99, 100]

sorted排序函数

Python内置的sorted()函数能够对list进行排序,例如:

>>> sorted([1,3,5,2,9,7,4,6,8])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

同时sorted函数还是一个高阶函数,它还能够接收一个比较函数来实现自定义排序的功能。

例如,我们想实现一个排倒序的功能,那么我们可以自定义一个排倒序的比较函数,然后将这个函数传递到sorted函数中:

>>> def reversed_cmp(x,y):
...     if x < y:
...             return 1
...     if x > y:
...             return -1
...     return 0
... 
>>> sorted([1,3,5,2,9,7,4,6,8],reversed_cmp)
[9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> 

3.2 返回函数

函数除了可以作为另外一个函数的参数之外,还可以作为函数的返回值。

下面就以一个求和函数为例:

>>> def lazy_sum(*args):
...     def sum():
...         ax = 0
...         for n in args:
...             ax = ax + n
...         return ax
...     return sum

当我们调用lazy_sum()时,返回的不是最后计算得到的数值,而是一个函数对象

>>> f = lazy_sum(1,3,5,7,9)
>>> f
<function sum at 0x7f55b35b96e0>

只有再调用函数f()之后才能得到求和的结果:

>>> f()
25

在这个例子中,我们在lazy_sum()函数中还定义了一个sum()函数,并且内部函数sum还引用了外部函数lazy_sum函数中的参数和局部变量,当lazy_sum函数返回函数sum时 ,相关参数和局部变量都保存在返回函数值中,这种程序成为“闭包”。

闭包的使用

闭包的使用虽然简单,但是它有许多需要注意的地方,下面就举个例子:

>>> def count():
...     fs = []
...     for i in range(1,4):
...             def f():
...                 return i*i
...             fs.append(f)
...     return fs
... 
>>> f1,f2,f3 = count()

在上面例子中,每次循环都建立了一个新的函数,然后将3个函数都通过列表的形式返回了。

你可能认为,三个函数的执行结果分别是1,4,9,但是实际的运行结果则是:

>>> f1()
9
>>> f2()
9
>>> f3()
9

三个函数的运行结果全部都是9!这是因为函数f在定义中引用了局部变量i,并且函数f并不是立即运行。等到三个函数都返回时,保存在返回函数中的局部变量i的值已经都变成了3,因此最终的结果都是9。

所以在返回闭包时一定要注意,千万不要在返回的函数中使用循环中的变量,或者后续会发生变化的变量

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

>>> def count():
...     fs=[]
...     for i in range(1,4):
...         def f(j):
...             def g():
...                 return j*j
...             return g
...         fs.append(f(i))
...     return fs
>>> f1()
1
>>> f2()
4
>>> f3()
9

3.3 匿名函数

当我们在传入函数时,有些时候我们可以不用显示地定义一个函数,而直接传入匿名函数更为方便。

以map()函数为例,计算f(x)=x*x时,除了定义一个f(x)之外,还可以直接传入匿名函数:

>>> map(lambda x:x*x,[1,2,3,4,5])
[1, 4, 9, 16, 25]

通过对比可以知道,lambda x:x*x实际上等效于:

def f(x):
	return x*x

从上面的例子中,我们可以总结一下匿名函数的定义形式:

lambda 形参:返回值表达式

其中关键字lambda表示匿名函数,冒号前面是形参,冒号后面就是返回值表达式。但是从上面的匿名函数定义形式我们就可以看出,匿名函数有个限制,就是它的返回值只能有一个,因为冒号后面只允许有一个返回值表达式。

用匿名函数有一个好处,因为没有函数名,就不存在函数名冲突。此外,匿名函数还是一个函数对象,还可以将匿名函数对象赋值给一个变量,然后就可以通过该变量来调用该函数,例如:

>>> f = lambda x:x*x
>>> f
<function <lambda> at 0x7f55b35b98c0>
>>> f(5)
25

同样,匿名函数也可以作为返回值返回。

3.4 decorator函数(装饰器)

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

>>> def now():
...     print '2015-07-20'
... 
>>> now._name_
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute '_name_'
>>> now.__name__
'now'
>>> g = now
>>> g.__name__
'now'

在这里要注意一点: “__name__“属性中“name”前面和后面均有两个英文下划线

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

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

>>> def log(func):
...     def wrapper(*args,**kw):
...         print 'call %s():'%(func.__name__)
...         return func(*args,**kw)
...     return wrapper

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

@log
def now():
	print '2015-07-20'

把@log放在now()函数的定义处,相当于执行了语句:

now = log(now)

下面,我们就对上述的过程进行一个分析: decorator_analysis_process

如果decorator需要传入参数,那就需要编写一个返回decorator的高阶函数,例如:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

运行的结果是:

>>> now()
execute now():
2013-12-25

上面的语句的执行效果等效于:

>>> now = log('execute')(now)

整个执行的过程,如下图所示: decorator_analysis_process2

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的’now’变成了’wrapper’:

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写 wrapper.name = func.name 这样的代码,因为Python内置的functools.wraps就是干这事的,所以一个完整的decorator的写法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print 'call %s():' % func.__name__
        return func(*args, **kw)
    return wrapper

或者是带参数的decorator

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')
def now():
	print '2015-07-20'

now()

print 'now.__name__: %s' % now.__name__

后者运行的结果为:

execute now():
2015-07-20
now.__name__: now

import functools是导入functools模块。

请编写一个decorator,能在函数调用的前后打印出’begin call’和’end call’的日志。

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print '%s %s():' % ('begin call', func.__name__)
        func(*args, **kw)
	print '%s %s():' % ('end call', func.__name__)
    return wrapper

@log
def now():
	print '2015-07-20'

now()

运行得到的结果:

begin call now(): 2015-07-20 end call now(): now.name: now

编写一个@log的decorator,使它既支持:

@log
def f():
	pass

又支持:

@log('execute')
def f():
	pass

从上面这个需求分析,我们可以采用默认参数的方法来解决上面这个问题:

import functools

def log(text=None):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
			if text == None:
				print '%s():' % (func.__name__)
			else:
				print '%s %s():' % (text, func.__name__)
			return func(*args,**kw)
        return wrapper
    return decorator

@log('execute')
def now():
	print '2015-07-20-now()'

@log()
def now1():
	print '2015-07-20-now1()'

now()

now1()

上面运行的结果是:

execute now(): 2015-07-20-now() now1(): 2015-07-20-now1()

3.5 偏函数

Python中的functools模块提供了许多有用的功能,其中一个就是偏函数(Partial Function)。要注意这里的偏函数和数学上的偏函数的意义不一样。

下面就以int()函数为例,来介绍偏函数。

int()函数的功能是将字符串形式的整数转换成整型的整数形式,例如:

>>> int('12345')
12345

但是int()还提供了额外的参数base,默认值为10。如果传入base的参数为N,那么int()就可以作N进制的转换。例如:

>>> int('123',base=8)
83
>>> int('10000000',base=2)
128

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

>>> def int2(x):
...     return int(x,base=2)
...
>>> int2('1000')
8

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

>>> import functools
>>> int2 = functools.partial(int,base=2)
>>> int2('1000')
8

简单总结一下functools.partial的作用就是将函数中的一些参数固定(将参数设置为默认值),然后返回一个新函数,通过这个函数进行调用就会变得非常简单。当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。