第七章:函数

7.1 可接受任意数量参数的函数

以使用 * 参数。

以 ** 开头的参数

7.2 只接受关键字参数的函数

1
2
def a(x, *args, y):
pass

此时的y称为强制关键字参数

7.3 给函数参数增加元信息

使用函数参数注解

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

函数注解只存储在函数的 __annotations__ 属性

7.4 返回多个值的函数

函数直接 return 一个元组就行了

7.5 定义有默认参数的函数

1
2
def spam(a, b=42):
print(a, b)

检查默认参数有没有值传进来

1
2
3
4
5
6
7
8
9
_no_value = object()

def func(a,b=_no_value):
if b is _no_value:
print('b 没有被传进来')


func(1) # b 没有被传进来
func(1,2)
  • object 是 python 中所有类的基类。
  • 你可以创建 object 类的实例,但是这些实例没什么实际用处,因为它并没有任何有用的方法,也没有任何实例数据
  • 因为它没有任何的实例字典,你甚至都不能设置任何属性值
  • ==object()唯一能做的就是测试同一性==。

7.6 定义匿名或内联函数

lambda 表达式

7.7 匿名函数捕获变量值(重要)

注意:

  • python普通函数的默认值在定义时绑定
  • lambda 表达式的变量是自由变量,在运行时绑定值,而不是定义时就绑定
  • 两者完全相反
1
2
3
4
5
6
7
8
# 普通函数的默认值在定义时绑定
# 所以y的默认值为1
x = 1
def func(y=x):
print(y)
x = 2

func() # 1
1
2
3
4
5
6
7
# 因为lambda在运行时绑定
# 所以x=2
x = 1
func = lambda y : x + y
x = 2

print(func(10)) # 12

lambda使用默认值:

1
2
func = lambda x=1 :x + 5
print(func()) # 6

通过使用函数默认值参数形式,lambda 函数在定义时就能绑定到值

1
2
3
4
5
6
7
8
funcs = [lambda x: x+n for n in range(5)]
for f in funcs:
print(f(0))
# 4
# 4
# 4
# 4
# 4
1
2
3
4
5
6
7
8
funcs = [lambda x, n=n: x+n for n in range(5)]
for f in funcs:
print(f(0))
# 0
# 1
# 2
# 3
# 4

7.8 减少可调用对象的参数个数

使用 functools.partial() 。

functools.partial()经常用来绑定回调函数 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import logging
from multiprocessing import Pool
from functools import partial


def output_result(result, log=None):
if log:
log.debug('Got: %r', result)


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

if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger('test')

p = Pool()
# 通过使用 partial() 传递额外的 logging参数。
p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
p.close()
p.join()
# DEBUG:test:Got: 7

很多时候 partial() 能实现的效果,lambda 表达式也能实现

1
p.apply_async(add, (3, 4), callback=lambda result: output_result(result,log))

7.9 将单方法的类转换为函数

使用闭包来将单个方法的类转换成函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
from urllib.request import urlopen

class UrlTemplate:
def __init__(self, template):
self.template = template
def open(self, **kwargs):
return urlopen(self.template.format_map(kwargs))

# 转为闭包
def urltemplate(template):
def opener(**kwargs):
return urlopen(template.format_map(kwargs))
return opener
  • 大部分情况下,你拥有一个单方法类的原因是需要存储某些额外的状态来给方法
    使用。
  • 比如,定义 UrlTemplate 类的唯一目的就是先在某个地方存储模板值,以便将来可以在 open() 方法中使用。
  • 简单来讲,一个闭包就是一个函数,只不过在函数内部带上了一个额外的变量环境。
  • 闭包关键特点就是它会记住自己被定义时的环境

7.10 带额外状态信息的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def apply_async(func, args, *, callback):
result = func(*args)
callback(result)


def print_result(result):
print('Got:', result)


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

apply_async(add, (2, 3), callback=print_result)
# Got: 5
apply_async(add, ('hello', 'world'), callback=print_result)
# Got: helloworld

为了让回调函数访问外部信息,一种方法是使用一个绑定方法来代替一个简单函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ResultHandler:
def __init__(self):
self.sequence = 0

def handler(self, result):
self.sequence += 1
print('[{}] Got: {}'.format(self.sequence, result))


def apply_async(func, args, *, callback):
result = func(*args)
callback(result)

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


r = ResultHandler()
apply_async(add, (2, 3), callback=r.handler)
# [1] Got: 5
apply_async(add, ('hello', 'world'), callback=r.handler)
# [2] Got: helloworld
  • 至少有两种主要方式来捕获和保存状态信息,
  • 在一个对象实例 (通过一个绑定方法) 或者在一个闭包中保存它。
  • 两种方式相比,闭包或许是更加轻量级和自然一点,因为它们可以很简单的通过函数来构造。它们还能自动捕获所有被使用到的变量。因此,你无需去担心如何去存储额外的状态信息 (代码中自动判定)。
  • 更高级的方式是使用协程

7.11 内联回调函数

回调函数很容易弄乱程序控制流。你希望找到某个方法来让代码看上去更像是一个普通的执行序列。

使用生成器和协程可以使得回调函数内联在某个函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from queue import Queue
from functools import wraps


def apply_async(func, args, *, callback):
result = func(*args)
callback(result)


class Async:
def __init__(self, func, args):
self.func = func
self.args = args


def inlined_async(func):
@wraps(func)
def wrapper(*args):
f = func(*args)
result_queue = Queue()
result_queue.put(None)
while True:
print('------------')
result = result_queue.get()
try:
print('************',result)
a = f.send(result)
print('++++++++++++++++',a)
apply_async(a.func, a.args, callback=result_queue.put)
except StopIteration:
break
return wrapper


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

@inlined_async
def test():
print('///////')
r = yield Async(add, (2, 3))
print('111111111',r)
print(r)
r = yield Async(add, ('hello', 'world'))
print(r)
for n in range(10):
r = yield Async(add, (n, n))
print(r)
print('Goodbye')

test()
# ------------
# ************ None
# ///////
# ++++++++++++++++ <__main__.Async object at 0x0000020144B22E48>
# ------------
# ************ 5
# 111111111 5
# 5
# ++++++++++++++++ <__main__.Async object at 0x0000020144B28320>
# ------------
# ************ helloworld
# helloworld
# ++++++++++++++++ <__main__.Async object at 0x0000020144B22E48>
# ------------
# ************ 0
# 0
# ++++++++++++++++ <__main__.Async object at 0x0000020144B28320>
# ------------
# ************ 2
# 2
# ++++++++++++++++ <__main__.Async object at 0x0000020144B22E48>
# ------------
# ************ 4
# 4
# ++++++++++++++++ <__main__.Async object at 0x0000020144B28320>
# ------------
# ************ 6
# 6
# ++++++++++++++++ <__main__.Async object at 0x0000020144B22E48>
# ------------
# ************ 8
# 8
# ++++++++++++++++ <__main__.Async object at 0x0000020144B28320>
# ------------
# ************ 10
# 10
# ++++++++++++++++ <__main__.Async object at 0x0000020144B22E48>
# ------------
# ************ 12
# 12
# ++++++++++++++++ <__main__.Async object at 0x0000020144B28320>
# ------------
# ************ 14
# 14
# ++++++++++++++++ <__main__.Async object at 0x0000020144B22E48>
# ------------
# ************ 16
# 16
# ++++++++++++++++ <__main__.Async object at 0x0000020144B28320>
# ------------
# ************ 18
# 18
# Goodbye

7.12 访问闭包中定义的变量

通常来讲,闭包的内部变量对于外界来讲是完全隐藏的。
但是,你可以通过编写访问函数并将其作为函数属性绑定到闭包上来实现这个目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def sample():
n = 0
def func():
print('n=', n)

def get_n():
return n

def set_n(value):
nonlocal n
n = value

func.get_n = get_n
func.set_n = set_n
return func

f = sample()
f() # n= 0

f.set_n(10)
f() # n= 10

甚至你还可以将它们封装成一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sys

class ClosureInstance:
def __init__(self, locals=None):
if locals is None:
locals = sys._getframe(1).f_locals
# Update instance dictionary with callables
self.__dict__.update((key,value) for key, value in locals.items() if callable(value))

def __len__(self):
return self.__dict__['__len__']()


def Stack():
items = []

def push(item):
items.append(item)
def pop():
return items.pop()
def __len__():
return len(items)

return ClosureInstance()

s = Stack()
s.push(10)
s.push(20)
s.push('Hello')
print(len(s))# 3

print(s.pop())# Hello
print(s.pop())# 20
print(s.pop())# 10