Skip to main content

4.函数和装饰器

函数定义与判断

定义函数使用 def 关键字。三连双引号间的内容被视为函数注释。

def func(a, b=0):
"""
This is a function that can meow.
"""
return " ".join(["meow"] * (a + b))

调用函数:

func(2)  # 单参数,仅 a 
'meow meow'
func(2, 3) # 双参数, a 与 b 都被传入
'meow meow meow meow meow'
help(func)
Help on function func in module __main__:

func(a, b=0)
This is a function that can meow.

通过 callable() 可以判断一个对象是否是一个可调用的函数:

callable(func)
True

不定参函数

利用序列(或元组)与字典,向函数传参。前者在传入时需要加上一个星号,后者需要两个。

lst = [1, 3, 4]
d = {"a": 2, "b": 3, "c": 5}
print("{}+{}={}".format(*lst), "{a}+{b}={c}".format(**d))
1+3=4 2+3=5

zip 函数

zip() 函数的作用是“合并”多个列表为一个。其返回值是一个列表,列表内的元素类型是元组。如果待合并的列表长度不同,以最短的为准。

a = [1, 2, 3, 4]
b = [5 ,6, 7]
c = "abcd"
list(zip(a, b, c))
[(1, 5, 'a'), (2, 6, 'b'), (3, 7, 'c')]

它比较常用于交换字典的键与值:

dict(zip(d.values(), d.keys()))
{2: 'a', 3: 'b', 5: 'c'}

同时遍历两个或更多的序列,可以使用 zip() 组合:

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
... print('What is your {0}? It is {1}.'.format(q, a))
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.

lambda 匿名函数

一种匿名函数的声明方式,格式如下:

func = lambda x, y: x + y
func(2, 5)
7

map 函数

map() 能够对传入的序列进行依次操作,并将结果返回为一个可转换为列表的 map 对象。通常列表解析(或生成器解析)可以实现与其同样的工作。

lst = list(map(lambda x: x + 1, range (5)))
print(lst)
[1, 2, 3, 4, 5]
f = lambda x: x + 1
[f(x) for x in range(5)]
[1, 2, 3, 4, 5]

filter 函数

给定序列,对于满足某规则的部分(即 True),予以返回。

list(filter(lambda x: x > 0, range(-3, 3)))
[1, 2]

reduce 函数

该函数在 Python 2 中是可以直接调用的,但在 Python 3 中需要从 functools 模块进行调用。

from functools import reduce
reduce(lambda x, y: x + y, range (5)) # 0+1+2+3+4
10

enumerate 函数

在序列中遍历时,索引位置和对应值可以使用 enumerate() 函数同时得到

它允许你像 d.items() 那样,用类似的方式操作列表:

a = [1, 3, 5]
for i, v in enumerate(a):
print("lst[{}] = {}".format(i, v))

lst[0] = 1
lst[1] = 3
lst[2] = 5

相同效果:
for k, v in a.items():
print("lst[{}] = {}".format(k, v))

装饰器

装饰器是函数的函数——传入的参数是一个函数,返回的值也是一个函数。相当于一个函数集到另一个函数集的映射,可以理解为数学意义上的算子。

首先来看一个简单的例子:函数可以被赋值给一个变量。

def pyrint(data="Python"):
return data.upper()

f = pyrint
f()
'PYTHON'

还可以通过 __name__ 来得到当前函数的名称:

f.__name__
'pyrint'

一个函数定义可以被一个或多个 decorator 表达式所包装。 当函数被定义时将在包含该函数定义的作用域中对装饰器表达式求值。 求值结果必须是一个可调用对象,它会以该函数对象作为唯一参数被发起调用。 其返回值将被绑定到函数名称而非函数对象。 多个装饰器会以嵌套方式被应用。 例如以下代码

@f1(arg)
@f2
def func(): pass
#大致等价于
def func(): pass
func = f1(arg)(f2(func))

那什么时候需要装饰器呢?比如在函数需要被重用、但又不能直接改写 def 的场合(在维护中应该不少见吧!)。例如,我们希望在返回值之前,把函数名也打印出来:

def showname(func):
def subfunc(*args, **kwarg):
print("FUNCTION {} called.".format(func.__name__))
return func(*args, **kwarg)
return subfunc

这样如果我们通过 showname(pyrint) 这种形式,就能够在 pyrint 函数被调用之前,额外打印一行内容。

想要改动该函数,不需要改动 def 语句以下的内容,只需要用 @showname 命令来应用这个装饰器:

@showname
def pyrint(data="Python"):
return data.upper()
pyrint()
FUNCTION pyrint called.

'PYTHON'

如果装饰器需要传递参数,那么,需要在定义时,外层再嵌套一个函数:

def showname(num=1):
def decorator(func):
def subfunc(*args, **kwarg):
print("Call time: {}. FUNCTION {} called.".format(num, func.__name__))
return func(*args, **kwarg)
return subfunc
return decorator

@showname(2)
def pyrint(data="Python"):
return data.upper()

pyrint()
Call time: 2. FUNCTION pyrint called.

'PYTHON'

不过装饰器被应用于函数定义之前时,函数的 __name__ 属性会改变。比如上例:

pyrint.__name__
'subfunc'
使用模块 functools 来解决这一问题:

import functools

def showname(num=1):
def decorator(func):
@functools.wraps(func) # 加上这一行
def subfunc(*args, **kwarg):
print("Call time: {}. FUNCTION {} called.".format(num, func.__name__))
return func(*args, **kwarg)
return subfunc
return decorator

@showname(2)
def pyrint(data="Python"):
return data.upper()

pyrint.__name__
'pyrint'