第七章 函数
7.1 可接受任意数量参数的函数
* 参数接受任意数量的位置参数,放在最后一个位置参数后,接收到的参数组成一个元组;
** 参数接受任意数量的关键字参数,放置在最后一位,接收到的参数组成一个字典。
* 参数后面仍然可以定义其它参数,即强制关键字参数。
7.2 只接收关键字参数的函数
* 参数或单个 * 后面的参数必须用关键字传参。
7.3 给函数参数增加元信息
使用函数参数注解:
def add(x:int, y:int) -> int:
return x + y
这些注解不影响运行效果,但增加代码可读性。
函数注解存储在函数的 __annotations__ 属性中。
>>> add.__annotations__
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
7.4 返回多个值的函数
函数返回的多个值用逗号分隔,会将这些值包装为一个元组返回。
7.5 定义有默认参数的函数
函数定义时直接给参数指定默认值。
默认参数只在函数定义时赋值一次。
>>> x = 3
>>> def test(a, b=x):
... print(a, b)
...
>>> test(1)
1 3
>>> x = 5
>>> test(1)
1 3
默认参数的值应该是不可变对象。因为默认值如果发生改变,再次调用这个函数时默认值就不一样了。如果默认参数需要是一个可修改的容器,可以使用 None 当默认值。对 None 的测试要使用 is 操作符。
>>> def test(a, b=[]):
... print(b)
... return b
...
>>> x = test(1)
>>> x
[]
>>> x.append(4)
>>> x
[4]
>>> test(2)
[4]
如果不想传默认值,而是只想检测有没有传进来东西,而 False、None、0 都是合法的值。这时需要定义一个私有对象。
_no_value = object()
def test(a, b=_no_value):
if b is _no_value:
print('no value')
用户不可能传递 object 的实例进来的。
7.6 定义匿名或内联函数
lambda 表达式能用来代替一些简单的函数。
lambda 只能指定单个表达式,不能包含更多的语句、条件表达式、迭代或异常处理等。
在排序和 reduce 操作时经常会用。
7.7 匿名函数捕获变量值
默认参数是在定义时赋值,而 lambda 表达式中的自由变量在调用时才会绑定值。
>>> x = 10
>>> a = lambda y: x + y
>>> x = 30
>>> a(0)
30
如果想在定义时就绑定值,可以把参数定义为默认参数:
>>> x = 10
>>> a = lambda y, x=x: x+y
>>> x = 20
>>> a(0)
10
最容易犯错的地方是在列表推导式使用 lambda 表达式的时候。
>>> testlist = [lambda x: x+i for i in range(5)]
>>> for t in testlist:
... print(t(0))
...
4
4
4
4
4
把参数 i 修改为默认参数,就能在定义时就绑定值。
>>> testlist = [lambda x, i=i: x+i for i in range(5)]
>>> for t in testlist:
... print(t(0))
...
0
1
2
3
4
7.8 减少可调用对象的参数个数
functools.partial() 函数可以为参数设置固定的值,返回一个可调用对象,
这个对象接收未被赋值的参数,然后跟已复制的参数合并起来一同传给原函数。
7.9 将单方法的类转换成函数
如果一个类只是为了给其中的某个方法提供状态信息的话,使用闭包能将其转换为函数。
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))
yahoo = UrlTemplate('http://finance.yahoo.com/dquotes.csv?s={name}&f={fields}')
for line in yahoo.open(name='IBM,AAPL,FB',fields='sliciv'):
print(line.decode('utf-8'))
上述实现使用闭包:
def urltemplate(template):
def opener(**kwargs):
return urlopen(template.format_map(kwargs))
return opener
yahoo = urltemplate('http://finance.yahoo.com/dquotes.csv?s={name}&f={fields}')
for line in yahoo(name='IBM,AAPL,FB',fields='sliciv'):
print(line.decode('utf-8'))
闭包在函数内部有一个额外的变量环境,它会记住自己被定义时的环境。
7.10 带额外状态信息的回调函数
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)
# 5
callback 这个参数是外部定义的函数,只接收一个参数。在想让这个函数访问额外的信息时会很麻烦。
# 类绑定方法
class ResultHandler:
def __init__(self):
self.sequence = 0
def handler(self):
self.sequence += 1
print(f'{self.sequence} Got: {result}')
r = ResultHandler()
apply_async(add, (2,3), callback=r.handler)
# 闭包
def make_handler():
sequence = 0
def handler(result):
nonlocal sequence # 修改须先声明
sequence += 1
print(f'{sequence} Got: {result}')
return handler
handler = make_handler()
apply_async(add, (2, 3), callback=handler)
# 协程
def make_handler():
sequence = 0
while True:
result = yield
sequence += 1
print(f'{sequence} Got: {result}')
handler = make_handler()
next(handler)
apply_async(add, (2, 3), callback=handler.send)
7.11 内联回调函数
回调函数、生成器、控制流。
将复杂的控制流隐藏在生成器函数背后。
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):
print(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, ('ok', 'go'))
print(r)
for n in range(10):
r = yield Async(add, (n, n))
print('test', r)
7.12 访问闭包中定义的变量
可以在闭包内编写访问函数并将其作为函数属性绑定到闭包上。
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)
n= 10
>>> f.get_n()
10
上述代码的扩展:让闭包模拟类的实例。这样写会比使用类稍微快一些。
import sys
class ClosureInstance:
def __init__(self, locals=None):
if locals is None:
locals = sys._getframe(1).f_locals
# 把局部变量中可调用的对象导入字典
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
<__main__.ClosureInstance object at 0x0000005EF0145A58>
>>> s.push(3)
>>> s.push(4)
>>> s.push(5)
>>> len(s)
3
>>> s.pop()
5
>>> s.pop()
4