8. 函数的奇淫巧技
- 函数中任意数量参数
如果希望能够接受任意数量的参数,可以使用一个 * 参数
def avg(first, *rest):
return (first + sum(rest)) / (1 + len(rest)) |
这样我们可以使用avg(1,2) avg(1,2,3,4)
first为1,剩下的都加入rest,而rest实际上是一个元组,我们可以将其当作序列来使用
如果希望接受任意数量的关键字参数,则可以使用 ** 开头的参数,比如
def make_element(name, value, **attrs):
keyvals = [‘ %s=”%s”‘ % item for item in attrs.items()] attr_str = ”.join(keyvals) print(attr_str) |
传入一个字典,其中key是关键字,value是实际参数
我们还可以同时在一个函数中使用 * **
def anyargs(*args, **kwargs):
print(args) # A tuple print(kwargs) # A dict |
需要注意的是 ** 参数必须要在在最后 而*参数后仍然可以定义其他的参数
那么* 参数后面的参数,就必须要使用关键字进行传输
def recv(maxsize, *, block):
‘Receives a message’ pass recv(1024, block=True) |
- 函数参数的元数据
如果希望给函数的参数增加一些额外信息,那么可以使用函数参数注解
def add(x:int, y:int) -> int:
return x + y |
虽然这些注解不会进行类型检查,但是方便阅读源码,知道这个函数参数的含义
- 一个函数返回多个值
在return的时候直接返回就行,只不过会被封装为一个元组
>>> def myfun():
… return 1, 2, 3
- 函数中的默认参数
如果想要定义默认参数,直接在参数中指定就可以了
def spam(a, b=42):
print(a, b) |
当然默认参数可以传入变量,但是需要注意,默认参数的值只有在函数定义的时候赋值一次,比如
>>> x = 42
>>> def spam(a, b=x): … print(a, b) … >>> spam(1) 1 42 >>> x = 23 # Has no effect >>> spam(1) 1 42 |
而且默认参数理应不能变,不然会进行记录变化,比如默认参数是一个数组
>>> def spam(a, b=[]):
… print(b) … return b >>> x.append(99) >>> x.append(‘Yow!’) >>> spam(1) # Modified list gets returned! [99, ‘Yow!’ |
- 定义匿名函数
这里简单说下lambda的使用方式
>>> add = lambda x, y: x + y
>>> add(2,3)
5
这样的一个简单的匿名函数,非常适合表达计算表达式
不过需要注意,这样的一个匿名表达式,他的变量传递跟一般函数是不一样的,只有运行时的时候赋值变量,就比如
>>> x = 10
>>> a = lambda y: x + y
>>> x = 20
>>> b = a
>>> a(10)
30
>>> b(10)
30
如果想要进行值的绑定的话,则应该在定义的时候就捕获对应的值
>>> x = 10
>>> a = lambda y, x=x: x + y >>> x = 20 >>> b = lambda y, x=x: x + y >>> a(10) 20 >>> b(10) 30 |
如果是在循环中使用lambda的话,那么应该进行值的捕获
>>> funcs = [lambda x, n=n: x+n for n in range(5)]
>>> for f in funcs: … print(f(0)) |
减少方法的参数个数
如果希望减少某个函数的参数类型,可以使用functools.partial() 其可以给一个函数设置一到多个固定值
def spam(a, b, c, d):
print(a, b, c, d) >>> s2 = partial(spam, d=42) # d = 42 >>> s2(1, 2, 3) 1 2 3 42 |
这个函数有什么用呢?
主要就是兼容一些要求不同数量的函数
比如我们想要进行排序,排序只需要一个参数的传入,但是我们的计算函数需要传入两个参数
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.sort(key=partial(distance,pt)) >>> points [(3, 4), (1, 2), (5, 6), (7, 8)] >>> |
这样就可以进行兼容了
- 单方法的类作为函数
如果我们有一个只有一个方法的类,这个类一般为了记录某些属性,方便在方法中使用,那么我们可以使用闭包来将其转换为函数
class UrlTemplate:
def __init__(self, template): self.template = template def open(self, **kwargs): return urlopen(self.template.format_map(kwargs)) # Example use. Download stock data from yahoo yahoo = UrlTemplate(‘http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}’) for line in yahoo.open(names=’IBM,AAPL,FB’, fields=’sl1c1v’): print(line.decode(‘utf-8’)) |
这样的一个类,我们在声明之后就为了使用其open函数
那么我们可以使用闭包进行转换
def urltemplate(template):
def opener(**kwargs): return urlopen(template.format_map(kwargs)) return opener # Example use yahoo = urltemplate(‘http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}’) for line in yahoo(names=’IBM,AAPL,FB’, fields=’sl1c1v’): print(line.decode(‘utf-8’)) |
我们需要一个地方进行存储这个类,我们不如直接使用闭包,opener函数就记住了传入的template的值,并且在接下来的调用时使用
- 回调函数保存额外信息
一般来说我,我们的回调函数一般都是
>>> def add(x, y):
… return x + y
>>> apply_async(add, (2, 3), callback=print_result)
Got: 5
如果希望这种回调函数带有额外的信息,最简单的是创建一个类保存额外变量
class ResultHandler:
def __init__(self): self.sequence = 0 def handler(self, result): self.sequence += 1 print(‘[{}] Got: {}’.format(self.sequence, result)) |
之后就可以创建一个类的实例,然后handler()绑定方法作为回调函数
>>> r = ResultHandler()
>>> apply_async(add, (2, 3), callback=r.handler)
[1] Got: 5
或者使用闭包进行保存状态值
def make_handler():
sequence = 0 def handler(result): nonlocal sequence sequence += 1 print(‘[{}] Got: {}’.format(sequence, result)) return handler |
需要注意的是我们使用nolocal语句指示变量会在解析来被修改
或则考虑使用partial() 以及 lambda进行修改
>>> apply_async(add, (2, 3), callback=lambda r: handler(r, seq))
- 访问闭包中定义的变量
如何访问闭包中的内部变量呢?可以考虑编写访问函数进行访问
def sample():
n = 0 # Closure function def func(): print(‘n=’, n) # Accessor methods for n def get_n(): return n def set_n(value): nonlocal n n = value # Attach as function attributes func.get_n = get_n func.set_n = set_n return func |
仍然需要注意使用nolocal进行修改
我们甚至可以使用闭包来模拟出一个类的实例,
def Stack():
items = [] def push(item): items.append(item) def pop(): return items.pop() def __len__(): return len(items) return ClosureInstance() |
能够基本和一个类一样使用,而且由于不用维护self,所以访问的更快,但是需要注意,在很多类的特性上,模拟起来会很麻烦
- 内联回调函数
类似一种异步转同步的思路
from queue import Queue
from functools import wraps def apply_async(func, args, *, callback): # Compute the result result = func(*args) # Invoke the callback with the result 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: result = result_queue.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, (2, 3)) 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’) if __name__ == ‘__main__’: test() |
利用了send会让yield语句在之后进行运行