Python 系列学习之三:函数 Function

前言

打算写一系列文章来记录自己学习 Python 3 的点滴;本篇将会重点介绍 python 有关函数的基本内容;

本文为作者的原创作品,转载需注明出处;

参数

归纳起来,python 总共支持五种不同的参数形式;

位置参数(必选参数)

先看一个计算 $x^2$ 的函数,

1
2
def power(x):
return x * x

对于函数power(x),参数x就是一个位置参数;其特性就是,每次调用的时候,必须传入一个合法的参数,才能调用,

1
2
3
4
5
6
7
8
>>> power(5)
25
>>> power(15)
225
>>> power()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'x'

可见,位置参数(positional argument)是必填的,所以,也称作必选参数

默认参数

1
2
3
4
5
6
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

上面就定义了一个默认参数 n,如果当调用该函数 power 的时候,如果不指定参数 n,那么其默认值的就是 2;

1
2
3
4
5
6
>>> power(5)
25
>>> power(5, 2)
25
>>> power(5, n=3)
125

可见,默认参数传递的过程中,可以是2也可以是n=2的形式;

注意一条约定俗成的规则,默认参数最好使用不可变对象,否则会出现 unexpected 的情况(这里只是最佳实践,当然你也可以不用遵守);举个例子,

1
2
3
4
5
6
7
def f(a, L=[]):
L.append(a)
return L

print(f(1))
print(f(2))
print(f(3))

输出,

1
2
3
[1]
[1, 2]
[1, 2, 3]

按照我们正常的编程思维,期望的结果自然是

1
2
3
[1]
[2]
[3]

但是,为什么每次函数调用,会使用到上次调用的参数 list 对象呢?答案就在 Python 解释器是如何解释该方法定义的了;Python 解释器会将方法

1
2
3
def f(a, L=[]):
L.append(a)
return L

解释为

1
2
3
l = []
def f(a, l):
....

也就是说,方法f的参数L=[]实际上被定义为了一个独立于方法体的全局变量,自然每次函数f调用的时候,都会重复引用到相同的参数,既全局变量 l;Ok,那既然默认参数的最佳实践是,不要去使用可变类型对象作为默认参数,那么,如果,我就是有这么一个可变类型 list 需要作为参数呢?那么就需要使用一些变通的方式了,下面,一般惯用的方式是使用None对象来替换可变类型参数,如下所述,

1
2
3
4
5
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L

这样,就能确保,在每次调用方法f之后,都在局部方法的内部创建了一个局部 list 变量 L;

可变参数

1
2
3
4
def calc(*numbers):
sum = 0
for n in numbers:
print(n)

如上所述,在方法参数名前面跟一个*号,表示该参数为可变参数;这样,我们就可以输入任意多个参数,包括传入 0 个参数;

1
2
3
4
5
>>> calc()
>>> calc(1,2,3)
1
2
3

那么,可变参数执行的原理是什么呢?其实,python 在执行上述可变参数的函数调用过程中,直接将所有的参数转换为一个tuple,既 (1,2,3),要注意的是,既然是tuple,那么在函数内部,是不允许对可变参数 numbers 进行更改的;

如果,我们已经有一个 list 或者 tuple 对象了,那么如何调用该可变参数函数呢?一种调用可变参数的方式是

1
2
3
4
5
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
1
2
3

但是,上面的调用方式过于笨拙,Python 为了简化其调用方式,可以直接在 list 和 tuple 对象的参数名前面加上*使其变为可变参数,这样我们就可以直接调用了;

1
2
3
4
5
>>> nums = [1, 2, 3]
>>> calc(*nums)
1
2
3

或者,

1
2
3
4
>>> calc(*[1,2,3])
1
2
3

综上,可变参数其实就是 Python 为了提供了使得数组能够成为参数的能力

关键字参数

1
2
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

如上,我们通过**kw定义了一个关键字参数,与任意参数不同,方法的执行过程中,关键字参数被转换为一个 Dict 对象;来看看下面调用的例子,

可以不传入关键字参数,

1
2
>>> person('Michael', 30)
name: Michael age: 30 other: {}

可以传入任意个数的关键字参数,注意,特别注意,这里约定,参数传递必须给出键的名字

1
2
3
4
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

类似于可变参数直接将 list 和 tuple 对象作为参数进行传递(调用过程中,在参数名前面加上*),这里同样可以在 dict 对象的参数名前面加上**作为参数直接调用;

1
2
3
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

可以看到通过**extra将 Dict 对象转换为关键字参数传入;不过这里要特别注意的是,通过**extra将 Dict extra 转变为关键自参数的传递过程,是值传递,也就是说,这里传递的是是 Dict extra 的一份副本,函数内部对 extra 的改变不会改变外部的 extra;为了验证,我们看看下面这个例子,

笔者对 person 方法内部做了些许变动,对传入的 extra 对象的键值做了变更,

1
2
3
def person(name, age, **kw):
kw['city']='Chengdu'
print('name:', name, 'age:', age, 'other:', kw)

调用,可以看到,内部的 extra 对象的 city 改成了 Chengdu;

1
2
3
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Bob', 20, **extra)
name: Bob age: 20 other: {'city': 'Chengdu', 'job': 'Engineer'}

可以看到,外部 extra 保持不变;

1
2
>>> extra
{'city': 'Beijing', 'job': 'Engineer'}

这就说明了,上述的参数传递过程是值传递

命名关键字参数

由于关键字参数,函数的调用者可以传入任意不受限制的关键字参数,所以,想要知道到底传入了哪些关键字参数,就必须在函数内部进行判断,

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)

所以,要知道关键字参数中是否传入了 city 或者 job,我们必须对传入的关键字参数 kw 的内容进行判断;

往往,我们需要限制关键字参数的名字,以限定传入的关键字参数;比如,我要限定传入的关键字参数仅有cityjob两个参数,该如何做呢?

命名关键字的常规定义方式

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

python 规定,通过一个符号*作为区分,后续的参数 cityjob 就是命名关键字参数;

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

并且只能有 cityjob 两个关键字参数;如果传入第三个为定义的关键字参数 age 则报错;

1
2
3
4
person('Jack', 24, city='Beijing', job='Engineer', age=20 )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() got multiple values for argument 'age'

含有可变参数的情况

如果参数中包含有一个可变参数,那么后续的参数自动的就成为命名关键字参数,也就是说 city 和 job 就是命名关键字参数;

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

命名关键字参数可以有缺省值

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

混合参数

上述五种参数类型都可以组合使用,但是,组合使用的时候,其参数定义的顺序必须是,位置参数(必选参数)、默认参数、可变参数、命名关键字参数和关键字参数;下面我们定义出两个不同的函数 _f1_ 和 _f2_ 来看看他们相关的调用情况,

1
2
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
1
2
3
4
5
6
7
8
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
1
2
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
1
2
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

tupledict 作为调用参数;

1
2
3
4
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

可变参数一节我们知道,可以通过在 tuple 或者 list 前面加上 * 使其变为可变参数,通过关键字参数小节我们知道,在 dict 前面加上 ** 使其变为关键字参数;上面,将 args 和 kw 分别作为可变参数和关键字参数传入方法 _f1_,好玩的事情便发生了,args 的头三个元素分别作为了位置参数值、默认参数值,最后一个作为可变参数值.. 这的确是好玩的地方;

1
2
3
4
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

调用 _f2_ 的情况就更好玩了,将关键字参数 kw 的一部分作为默认参数值,一部分作为关键字参数值;

由此,我们可以知道,对一任意的函数,都可以通过 *args**kw 的方式去调用他们;

内嵌函数和闭包

内嵌函数

闭包是嵌入式函数调用过程中的必然产物,也是函数式编程得以实现的底层基础;那么闭包是如何产生,它的作用范围又是如何的呢?笔者试图通过两个简单的程序来说明,进入 Python 3 shell,

1
2
3
4
5
6
def game():
score = 100
def increase_score():
score = score + 1
return score
return increase_score

笔者写该函数的目的是,试图返回一个内嵌函数 increase_score 的引用,并通过该引用调用此内嵌函数,在调用过程中,引用外部函数 game 的局部变量 score

执行,

1
2
3
4
5
6
>>> f = game()
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in increase_score
UnboundLocalError: local variable 'score' referenced before assignment

调用不成功,报错,说局部变量score在还没有被赋值的情况下就被引用了,所以不合法;不过,这个错误没有说清根本的原因,根本原因是,Python 在解释执行到 increase_score 内嵌函数内部语句 score = score + 1 时候,产生了歧义,等号左边的 score 告诉 python 解释器这是一个 increase_score 内嵌函数的局部变量,所以,python 解释器会把这里的 score 当做是函数 increase_score 的局部变量,而不再是引用的外部函数 game() 的局部变量 score,所以,自然也就报错,局部变量 score 在还没有被赋值的前提下(既是没有被初始化的前提下)就提前赋值了;

从上面的错误例子中,我们要得到的经验是,内嵌函数是无法直接通过赋值的方式去修改外部函数的局部变量的;这点,与 javascript 有很大的不同;所以,上述的代码应修改为,

1
2
3
4
5
6
def game():
score = 100
def increase_score():
s = score + 1
return s
return increase_score

以同样的方式执行,

1
2
3
>>> f = game()
>>> f()
101

这样呢,当 python 解释器解释执行到 increase_score 内嵌函数内部语句 s = score + 1 的时候,就会把 score 当做是外部函数 game() 的局部变量的引用,不再有之前所导致的歧义了;

闭包

那么闭包呢?你想说的闭包在哪呢?ok,别急,我们再来看看如下的几次调用,也许你会明白闭包的作用了,

1
2
3
4
5
6
7
>>> f()
101
>>> f()
101
>>> f()
101
...

可见,无论我们执行多少次,返回的总是 101,这能说明什么呢?对呀,是不是和你之前所认知的局部变量的概念有所矛盾呢?局部变量不就是用完既被释放掉吗?那为什么每次调用 f() 的时候,外部函数 game() 的局部变量 score = 100 一直没有被释放掉呢?每次调用,它都在那儿;是的,你真的发现了什么;对,你发现的就是闭包的特质,笔者试图用一句话来总结,那就是,“一个局部变量如果被其内嵌函数的引用所引用,如果内嵌函数的引用一直存在,那么该局部变量一直不会被销毁,除非,该内嵌函数的引用被销毁了。”

好的,我们将上述的例子在做一次调整,

1
2
3
4
5
def game(score):
def increase_score():
s = score + 1
return s
return increase_score

python 对基本变量的传递是值传递,所以,score 依然是 game 的局部变量;

变量作用域以及关键字 nonlocal 和 global 的作用

变量在 Python 的作用域非常的特殊,它的作用域可以是模块级别的,可以是类级别(或者实例级别),也可以是方法级别;这里,作者通过作用域在方法级别的变量来详细的描述一下nonlocalglobal 关键字的作用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def scope_test():
def do_local():
# print(spam)
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)

如上所述,我们在方法 scope_test() 中定义了局部变量 spam;看看执行调用的情况

1
2
3
4
>>> scope_test()
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam

我们一条一条的来分析对应的输出结果,

  1. 第一行输出结果,对应方法 do_local(),可见,内嵌方法是不能访问外部方法的局部变量 spam 的,可以通过在方法内部的第一行添加 print(spam) 来检验,添加以后,会报错,提示找不到 spam 变量;所以,do_local() 内部方法对 spam 的赋值并不会影响到 scope_test() 方法中的局部变量 spam,因为 python 解释器会将此两个变量视为两个不同的变量;

  2. 第二行输出结果,对应方法 do_nonlocal(),与 #1 不同的是,这里通过关键字nonlocal改变了 spam 的作用域,之前的作用域是绑定在方法 scope_test() 上的,nonlocal关键字的作用就是取消此绑定,可以供 scope_test() 方法内部的其它嵌入方法调用;注意,仍然不能在全局范围内被访问;

  3. 第三行输出结果,对应方法 do_global(),通过关键字global将局部变量 spam 的作用范围提升到了模块级别,可以通过如下的输出语句进行验证;

    1
    >>> print("In global scope:", spam)

匿名函数(lambda)

Python 通过关键字lambda来定义匿名函数,看个例子

1
>>> f = lambda x: x * x

执行一下,

1
2
>>> f(2)
4

可见,上述lambda函数的定义等价于

1
2
def f(x):
return x * x

要注意两点,

  1. 冒号:前面的 x 表示的是参数
  2. 由于lambda规定,只能写一行,所以,x * x的计算结果既作为返回值;

高阶函数

能够把函数作为参数传入的函数,这样的函数,我们称之为高阶函数;来看一个最简单的高阶函数,

1
2
def add(x, y, f):
return f(x) + f(y)

可见,函数 add() 的第三个参数 _f_ 接受的是一个函数的引用;执行以下,

1
2
>>> add(-5, 6, abs)
11

map/reduce

Python 的 map/reduce 衍生自 MapReduce: Simplified Data Processing on Large Clusters,也就是 10 多年前,Google 的那篇 Map/Reduce 的论文;所以,大体上,map 就是对输入的数据按照某种规则进行转换,可以理解为对原始数据进行清洗,并运算出想要得到的结果;然后 reduce 就是将各个节点上 map 以后的结果进行归并;

map

直接来看一个例子,

先定义一个函数 _f_,该函数就是后续对原始数据进行转换和清洗的规则;

1
2
3
>>> def f(x):
... return x * x
...

执行,

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

从上述执行的结果中,不难发现,通过函数map将输入的原始数据 [1, 2, 3, 4, 5, 6, 7, 8, 9] 通过规则 _f_ 进行了转化,进而得到了输出结果 [1, 4, 9, 16, 25, 36, 49, 64, 81]

Ok,那么下面,我们就来总结一下什么是mapmap函数接收两个参数,一个是转换规则,通常以一个函数的形式表示(备注,该函数只能接受一个参数),另外一个是需要被转换或者清洗的原始数据,接收的是一个 Iterable 类型的对象;map函数返回的是一个经过清洗和转换的 Iterable 对象;上面的例子中,通过list()函数将返回结果rIterable 对象的结果都打印了出来;

再来看一个典型的例子,我们需要把一个整形数组的内容全部替换为字符,怎么做呢?

1
2
3
>>> r = map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
['1', '2', '3', '4', '5', '6', '7', '8', '9']

Ok,map归纳起来,就是对原始数据通过某种规则进行转换,然后再输出转换以后的数据;

reduce

reduce的作用就是,将输入的数据,通过某种规则,两两归并,并输出归并以后的结果;

看一个非常简单的例子,对一个数组求和,来理解reduce的内部运行机制,

首先,定义归并的规则,该规则是一个函数,并且必须接受两个参数

1
2
def add(x, y):
return x + y

再次,通过reduce函数对数组求和;

1
2
3
>>> from functools import reduce
>>> reduce(add, [1, 3, 5, 7, 9])
25

Ok,从这个例子中,我们可以窥探到reduce的内部运行的机制,按照数组的顺序,进行两两求和,显示头两个求和,然后将其求和的结果再与第三个元素进行求和,以此类推,直到数组中的最后一个元素求和完毕;当然,上面的例子可以简单的用函数 sum 来完成,但是通过这个简单的例子,我们能够非常直接和容易的理解到reduce函数内部的运行机制;上述的运行机制可以通过下面的执行过程来归纳,

1
reduce(add, [1, 3, 5, 7, 9]) = add(add(add(add(1, 3), 5), 7), 9)

归纳起来就是,每次执行的结果作为过滤规则的下一次输入,与下一个元素进行归并运算;

map 和 reduce 结合使用

python 内置了int(str)方法,能够将字符串转换为 int;那么有没有一种非常简单的方式,来模拟实现 该 python 的内置方法int(str)呢?答案是有的,最简单的方式,就是使用mapreduce的组合方式;看下面的这个例子,

1
2
3
4
5
6
7
8
9
10
11
from functools import reduce

def str2int(s):
# 转变为整数
def fn(x, y):
return x * 10 + y
# 通过 char 映射为数字
def char2num(s):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]

return reduce(fn, map(char2num, s))

函数 str2int 返回的是一个 reduce 函数,该reduce函数接受一个map函数的返回结果作为原始数据的输入,map函数将输入的字符数组转换为对应的数字数组,并作为reduce的输入,最后,通过fn函数将数字数组进行归并运算,最终得到相应的整数 int;看看,执行的结果,

1
2
>>> str2int('12345678')
12345678

filter

filter的调用方式与mapreduce的调用方式非常的类似,也是接受两个参数,一个是过滤规则(一个函数,该函数只接受一个参数),一个是输入的原始数据(一个Iterator类型的对象);filter的作用是,根据过滤规则的返回值是True还是False决定保留还是丢弃该元素;

看一个非常简单的例子,过滤一个 list 中的元素,只保留偶数,去掉奇数;

首先,定义过滤规则,

1
2
def is_even(n):
return n % 2 == 0

其次,调用filter开始对偶数进行过滤,并得到筛选的结果,

1
2
3
>>> r = filter(is_even, [1, 2, 4, 5, 6, 9, 10, 15])
>>> list(r)
[2, 4, 6, 10]

sorted

顾名思义,sorted内置函数就是对某个数组进行排序所使用的;

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

但是,如果我们想对该数组中的绝对值进行排序呢?sorted函数同样可以接受一个规则,在排序之前,对原始数据进行转换,该规则对应一个函数,该函数仅接受一个参数;看看,我们如何对数组的绝对值进行排序;

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

通过默认参数 key 传入函数abs,该函数的作用就是为每一个元素进行绝对值转换;并将转换结果进行排序,也就得到了上述的结果;

再看一个例子,

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

可以看到,默认情况下,sorted是根据字符的 ASCII 码值进行比较排序的,由于Z < a,所以,Z排到了前面;

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

这样,在排序之前,对输入字母进行 lowercase 的转换,通过函数str.lower进行转换,那么就可以得到想要的输出了;

如果需要倒叙排序呢?

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

通过传入 reverse = True 参数即可实现;

装饰器

装饰器 Decorator,类似于 Spring 的 AOP,在不改动原有函数的前提下,对现有函数添加新的功能点;

两层嵌套

来看下面的这个例子,

1
2
def say_hello():
print('Hello World!')

如果我们想要在不改动该方法的基础上,在调用函数 say_hello 方法以前,输出当前的时间戳,该怎么做呢?如果是 Java,大家自然会想到 AOP 切面,那如果是 python 呢?那就是笔者要给大家所要介绍到的,那就是装饰器(Decorator);下面我们就来写这么一个装饰器,

1
2
3
4
5
6
7
8
import time

def log(func):
def wrapper(*args, **kw):
currenttime = time.localtime(time.time())
print(time.strftime('%Y-%m-%d %H:%M:%S', currenttime))
return func(*args, **kw)
return wrapper

上面,我们定义了一个装饰器 log,接受一个方法的引用作为参数 func,返回装饰器 log 的嵌入函数引用,在嵌入函数内部,我们追加了 AOP 的相关的逻辑,打印出当前的时间戳,然后再调用方法 func;可以看到,装饰器 log 就是一个包含嵌入函数的高阶函数;下面,我们看看如何来使用它;

1
2
3
@log
def say_hello():
print('Hello World!')

是的,使用它会非常的简单,直接在原有的方法上像注解一样加入该装饰器,@log;使用它,

1
2
3
>>> say_hello()
2017-07-04 15:35:05
Hello World!

可以看到,我们在方法 say_hello 方法中织入了新的逻辑,打印出了当前的时间戳;那么 python 是如何做到的呢?背后的原理是怎样的呢?很简单,当 python 的解释器解释执行到上述的@log语句的时候,会将其解释为调用如下的操作,

1
say_hello = log(say_hello)

ok,这下就非常清楚了,这就是设计模式中典型的装饰器模式,通过封装原有的对象,在调用过程中,返回一个新的对象给当前的引用 say_hello

三层嵌套

如果,我们需要给装饰器log传入参数,该怎么做呢?这个时候,我们需要三层嵌套函数来实现了;那么下面,笔者将试图将二层嵌套中的例子中的业务规则稍加改动,除了时间以外,我们希望加入是谁在调用,而谁调用,根据不同的用户将有不同的输入,所以,我们需要在装饰器log上传入参数;

1
2
3
4
5
6
7
8
9
10
11
import time

def log(who):
def decorator(func):
def wrapper(*args, **kw):
currenttime = time.localtime(time.time())
print(time.strftime('%Y-%m-%d %H:%M:%S', currenttime))
print("this is "+who+" just invokes...")
return func(*args, **kw)
return wrapper
return decorator
1
2
3
@log('Kane')
def say_hello():
print('Hello World!')

下面我们来执行一下,

1
2
3
4
>>> say_hello()
2017-07-04 16:02:12
this is Kane just invokes ~~~
Hello World!

可以看到,我们为装饰器传入了参数;

总结

装饰器的强大之处远不止于此,在调用原生方法之前,我们可以拦截参数,对参数进行过滤或者修改等;

偏函数

偏函数的作用就是将原有函数的某个参数进行固定,然后返回一个新的函数的引用;比如,我们有下面的这样一个简单的例子,函数 person,有一个默认参数是 gender,目前设置为’男’性;

1
2
def person(name, gender='男'):
print(name, gender)

那么,如果现在我们有这样一个新的需求,就是说,在沿用原来的方法 person 的前提下,但又不想改动源码的前提下,想新增一个默认参数 gender = ‘女’,那么该如何做呢?这个时候,就是偏函数发挥作用的地方了;

1
2
>>> import functools
>>> person2 = functools.partial(person, gender='女')

这样,我们通过偏函数,在原有带默认参数 gender=’男’ 的 person 函数的基础上,新建了一个默认参数 gender=’女’ 的一个新的函数的引用 person2;看看执行的效果,

1
2
>>> person2('Lucy')
Lucy 女

这样,person2 就在原有的函数 person 的基础上,构造得到了一个默认参数 gender = ‘女’ 的新的 person 函数的引用;it’s cool,至少,笔者在写到这里的时候,觉得 python 在各方面的扩展性上面做的非常的灵活;