Function
函数
可接受任意数量参数的函数
如果想要构造一个可接受任意数量参数的函数,可以使用 * 参数来定义该函数:
1 2 3 4 5
| def avg(first, *others): return (first + sum(others)) / (1 + len(others))
print(avg(1,2)) print(avg(1,2,3,4,5))
|
为了接受任意数量的关键字参数,可以使用以 ** 开头的参数定义函数:
1 2 3 4
| def foo(attr1, attr2, **attrs): print(attr1, attr2, attrs)
foo(1, 2, n=1, m=2)
|
综上所述,*args 和 **kwargs 分别是第一章介绍的压缩和字典参数。要求 *args 只能放在最后一个位置参数后面,**kwargs 只能放在最后一个参数后面。
只接受关键字参数的函数
将强制关键字参数放到某个 * 参数或者单个 * 后面就能达到函数的某些参数强制使用关键字参数传递的效果。
1 2 3 4 5
| def recv(maxsize, *, block): pass
recv(1024, True) recv(1024, block=True)
|
利用这个特性,可以达到以下的函数实现效果:
1 2 3 4 5 6 7 8
| def mininum(*values, clip=None): m = min(values) if clip is not None: m = clip if clip > m else m return m
mininum(1, 2, 3, -4, 5) mininum(1, 2, 3, -4, 5, clip=0)
|
这样使用的好处在于,如果函数调用者对本函数不是那么熟悉,直接传递前面几个参数并不会影响到他所想要的最终的结果的值。
给函数参数添加元信息
使用函数参数注解可以很好的提示程序员怎样正确的使用这个函数。例如:
1 2
| def add(x:int, y:int) -> int: return x+y
|
Python 解释器并不会对它有任何的语义,但是这种写法会对程序员较为友好(相当于静态语言)。函数注解存储在函数的 __annotations__ 属性中。注解的使用方法有很多(比如本方法或者注释说明),本处的作用主要还是文档。
返回多个值的函数
1 2 3 4
| def foo(): return 1, 2
a, b = foo()
|
定义有默认参数的函数
1 2 3 4
| def foo(a, b=10): return a+b foo(1) foo(1,2)
|
这里需要注意的一点是,带有的默认参数,应当是常量的值而不应当是一个变量,以防止对默认参数的改变造成不必要的麻烦。
定义匿名或内联函数
将简单的单行函数用 lambda 表达式代替的手法,即为匿名函数:
1 2 3
| add = lambda x, y: x+y add(2, 3) add('hello', 'world')
|
上述匿名函数的作用和下面的函数一致:
1 2
| def add(x, y): return x+y
|
lambda 表达式的典型使用场景是排序或者数据 reduce 等。
匿名函数捕捉变量值
这里涉及到一个新手常跳的坑,观察以下代码和结果:
1 2 3 4 5 6
| x = 10 a = lambda y: x+y x = 20 b = lambda y: x+y a(10) b(10)
|
其中的原因是 x 在这里是一个自由变量,在运行的时候才会绑定值,因此返回的结果会随着 x 的改变而变化。如果想要匿名函数在定义的时候就捕获到值,可以参考以下写法:
1 2 3 4 5 6
| x = 10 a = lambda y, x=x: x+y x = 20 b = lambda y, x=x: x+y a(10) b(10)
|
减少可调用对象的参数的个数
functools 中的 partial() 方法能固定某些参数并返回一个 callable 对象。这个新的 callable 对象接受未赋值的参数,然后根据之前已经赋值过的参数合并起来,最后将所有的参数一起传递给原始函数。
1 2 3 4 5 6 7 8 9 10 11
| from functools import partial
def spam(a, b, c, d): print(a, b, c, d)
s1 = partial(spam, 1) s1(2, 3, 4) s2 = partial(spam, d=42) s2(1, 2, 3) s3 = partial(spam, 1, 2, d=42) s3(15)
|
本函数功能主要是解决不兼容代码一起工作的问题,以下为示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| points = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
import math
def distance(p1, p2): x1, y1 = p1 x2, y2 = p2 return math.hypot(x2 - x1, y2 - y1)
pt = (4, 3)
points.sorted(key=partial(distance, pt)) print(points)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
def output_result(result, log=None): if log is not None: log.debug("Got:%r", result)
def add(x, y): return x + y
if __name__ == "__main__": import logging from multiprocessing import Pool from functools import partial
logging.basicConfig(level=logging.DEBUG) log = logging.getLogger('test')
p = Pool() p.apply_async(add, (3, 4), callback=partial(output_result, log=log)) p.close() p.join()
|
将单方法的类转换成函数
使用闭包将单方法的类转换为函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from urllib.request import urlopen
class UrlTemplate: def __init__(self, template): self.template = template
def open(self, **kwargs): return urlopen(self.template.formate_map(kwargs))
def urltemplate(template): def opener(**kwargs): return urlopen(template.format_map(kwargs)) return opener
test = UrlTemplate('http://www.test.com/s?date={date}&file={file}') test = urlTemplate('http://www.test.com/s?date={date}&file={file}')
for line in test(date="2018", file="csv"): print(line.decode('utf-8'))
|
待额外状态信息的回调函数
如果代码中需要依赖到回调函数的使用(比如事件处理器、等待后台任务完成后的回调等),并且你还需要让回调函数拥有额外的状态值。
1 2 3 4 5 6 7 8 9 10 11 12
| 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, ('Hello', 'World'), callback=print_result)
|
为了让灰调函数访问到外部信息,通常使用的方法包括:绑定类的方法,使用闭包实现类的功能和使用协程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ResultHandler:
def __init__(self): self.idx = 0
def hander(self, result): self.idx += 1 print('[{}] Got:{}'.format(self.idx, result))
r = ResultHandler() apply_async(add, ('Hello', 'World'), callback=r.handler)
apply_async(add, (1, 2), callback=r.handler)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def make_handler(): idx = 0 def handler(result): nonlocal idx idx += 1 print('[{}] Got:{}'.format(idx, result)) return handler
handler = make_handler() apply_async(add, ('Hello', 'World'), callback=handler)
apply_async(add, (1, 2), callback=handler)
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| def make_handler(): idx = 0 while True: result = yield idx += 1 print('[{}] Got:{}'.format(idx, result))
handler = make_handler() next(handler) apply_async(add, ('Hello', 'World'), callback=handler.send)
apply_async(add, (1, 2), callback=handler.send)
|
内联回调函数
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
| 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): @warps(func) def wrapper(*args): f = func(*args) result_queue = Queue() result_queue.put(None) while True: result = result.get() try: a = f.send(result) 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(): r = yield Async(add, (1, 2)) print(r) r = yield Async(add, ('Hello', 'World')) print(r) for n in range(3): r = yield Async(add, (n, n)) print(r)
|
本例中使用生成器函数的主要原因是,生成器函数中出现的暂停与需要实现的中断操作其实是类似的。具体来说就是 yield 操作会使一个生成器函数产生一个值并暂停,接下来调用的 __next__() 或 send() 方法又会让它从暂停的地方继续执行。
访问闭包中定义的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| def sample(): n = 0 def func(): print('n=', n) def get_n(): return n def set_n(): nonlocal n n = value
func.get_n = get_n func.set_n = set_n return func
f = sample() f() f.set_n(10) f() f.get_n()
|