0%

Python小技巧(6)--函数

函数----来源于cookbook第七章

记录第七章中比较有意思的部分


只接受关键词参数的函数

关键词参数放在*打头的参数后面或者放在一个单独的*后面即可

1
2
3
4
5
6
7
8
9
10

def func(x, *args, y):
...

def func(x, *, y):
...

func(10, y = 20) # 调用func
func(10, 20) # 错误

默认参数的问题

python函数的默认参数只会被初始化一次,所以只能赋值给不可变对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

_no_value = object() # 元类来构建出一个表示没有参数

# 请注意不传递参数和传递None的区别
def func(a, b = None):
if b is _no_value:
b = []
...

# 下面是一个错误示例

def func(l = []): # 请务必不要这么做
l.append(1)
print(l)

func() # 输出[1]
func() # 输出[1, 1]

匿名函数的一些问题

匿名函数即lambda函数,请时刻记住匿名函数是懒惰求值的

要固定下匿名函数中使用的参数,必须在匿名函数参数列表中确定

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

示例一 懒惰求值
x = 10
y = 1
a = lambda x:x+y
print(a(20)) # 21
y = 2
print(a(20)) # 22

# 示例二 绑定变量,此时y的值被定下来了
x = 10
y = 1
a = lambda x, y=y:x+y
print(a(20)) # 21
y = 2
print(a(20)) # 21

# 示例三 列表推导式
funs = [lambda x:x+n for n in range(5)]
for f in funs:
print(f(0)) # 4 4 4 4 4

# 示例四 列表推导式2
funs = [lambda x,n=n:x+n for n in range(5)]
for f in funs:
print(f(0)) # 0 1 2 3 4

固定多个参数的函数的某些参数

可以使用functools中的partial()工厂方法来实现。

1
2
3
4
5
6
7
8
9
10
11
12

from functools import partial

def func(a,b,c,d):
print(a,b,c,d)

func2 = partial(func, 1) # 即a = 1
func3 = partial(func, 1, 2, d =4) # 即a = 1, b = 2, d = 4

func2(-2,-3,-4) # 1 -2 -3 -4
func3(-3) # 1 2 -3 4

可以使用此方法将参数数目不兼容的代码组合到一起。

1
2
3
4
5
6
7
8
9
10
11

points = [(1,2),(3,4)] # 点集

import math
from functools import partial

def distance(p1,p2): # 求两点距离
return math.hypot(p1[0]-p2[0],p1[1]-p2[1])

points.sort(key = partial(distance,(0,0))) # 对点集求两点距离

闭包与单个函数的类

可以用闭包的工厂方法来代替只有单个函数的类。因为引入类的作用就是为了保存在类内可以使用的固定参数。

1
2
3
4
5
6
7
8
9

def template(template):
def opener(*args):
return template.format(args)
return opener

func = template("{} is {}") # 将常量保存在func内部
func(args)

闭包,但是可以修改参数

上述的闭包只是将传递的参数作为常量来使用,现在我们需要修改它,比如计数功能。

只需要在内层的函数内使用nonlocal关键字即可

1
2
3
4
5
6
7
8
9
10
11
12
13

def handle():
seq = 0
def inner_handle(result):
nonlocal seq
seq+=1
print(f"{seq} : {result}")
return inner_handle

h = handle()
h("first") # 1 : first
h("second") # 2 : second

协程与回调函数

可以利用yield关键字中断发送和接受数据的方式来构造协程。

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

# 示例一 : 重写可以修改参数的闭包
def handle():
seq = 0
while True:
result = yield
seq += 1
print(f"{seq} : {result}")

h = handle()
next(h) # 让协程执行到yield上
h.send("first") # 1 : first
h.send("second") # 2 : second


# 示例二 : 一个比较复杂的内联回调函数
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 inline_async(func): # 一个装饰器
@wraps(func)
def warpper(*args):
f = func(*args) # 先拿到生成器
print(f)
result_queue = Queue() # 创建一个队列
result_queue.put(None) # 队列初始值为None
while True:
result = result_queue.get() # 获取队列中的值
try:
a = f.send(result) # 然后把值发送给生成器,同时接受到值
# 下面把计算结果发送到队列中
apply_async(a.func, a.args, callback = result_queue.put)
except StopIteration:
break
return warpper

## 应用

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

@inline_async
def test():
r = yield Async(add,(2,3))
print(r)
r = yield Async(add,("hello ", "world"))
print(r)

这个例子感觉有些复杂,但其实它使用装饰器后可以简化在业务逻辑(test()函数)中处理回调函数(不然就得写成apply_async(add,(2,3),callback = ...)这种东西,需要手动去管理回调函数)

实现的原理是利用yield来获取到调用的函数和参数(被通过一个类封装了),然后执行计算、回调等任务,再使用send方法把这个值发送到业务逻辑中。

访问在闭包内的变量

一般来说闭包内的变量不太好被外界给访问和修改,但是我们可以创建存取函数来实现。

因为python中一切皆对象,所以闭包就可以当作轻量级的类来用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

def closure():
n = 0

def func():
print(f"n = {n}")

def get_():
return n

def set_(value):
nonlocal n
n = value

func.getn = get_
func.setn = set_
return func

f = closure()
f() # 0
f.get() # 0
f.set(10)
f.get() # 10

这个结构基本上就是一个类了,但是其运行速度会更快,更加轻量级。