1、python中的函数装饰器(Function decorator)用来“标记函数”,以某种方式增强函数的行为,其实就是一种语法糖(syntactic sugar),用来简化复杂的代码。如下:
可以用装饰器实现函数的替换,虽然这么做没什么意义:>>> def deco(func):
... def inner():
... print('running inner()')
... return inner #1
>>> @deco
... def target(): #2
... print('running target()')
...
>>> target() #3
running inner()
>>> target #4
<function deco.<locals>.inner at 0x10063b598>
还可以用作注册函数,对于新增的函数,只需要添加装饰器,而不用手动进行注册:registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
装饰器在函数定义后立即运行,这通常是在导入(import)时,例如上面代码的执行结果是:$ python3 registration.py
running register(<function f1 at 0x100631bf8>)
running register(<function f2 at 0x100631c80>)
running main()
registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>] running f1()
running f2()
running f3()
单独执行import操作,得到的结果是:>>> import registration
running register(<function f1 at 0x10063b1e0>)
running register(<function f2 at 0x10063b268>)
2、闭包(closures)指的是嵌套函数的作用于问题,如图,内层函数可以直接使用上层函数定义的变量,这种变量又叫做自由变量。
但自由变量只能使用,如果对自由变量进行赋值,就会被解释器当做局部变量而抛出“变量未定义”的异常,如下,这是由于python不要求像C一样先声明变量再使用变量,所以遇到对非全局变量的赋值操作会直接看作局部变量。def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'count' referenced before assignment
>>>
这种情况下不能使用global
关键字,因为没有涉及全局变量,而是嵌套函数中因为层次关系产生的相对外层的变量,这时候就要用nonlocal
关键字,用法和global
一样。def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
3、functools模块有两个实用的装饰器。lru_cache
用来缓存函数的中间结果,LRU即Least Recently Used,如下,在计算斐波那契数的递归过程中,计算过的值会保存在缓存中,减少重复计算。import functools
@functools.lru_cache()
def fibonacci(n):
if n<2:
return n
return fibonacci(n-2) + fibonacci(n-1)
singledispatch
装饰器用来生成泛函数(generic function),指的是根据函数参数的类型以不同方式执行操作。由于singledispatch
只根据函数第一个参数,所以称作单分派泛函数,与之对应的是多分派(multiple-dispatch)。用法如下:from functools import singledispatch
from collections import abc
import numbers
import html
@singledispatch
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content)
@htmlize.register(numbers.Integral)
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'
4、闭包是静态作用域(static scope)下的概念,与之相对的是动态作用域(dynamic scope)。静态作用域又叫做词法作用域(lexical scope),C、C++、Python、Java等大多数现在程序设计语言都是采用静态作用域规则,指的是变量的作用域是确定的,词法分析时不会逐层检查函数的调用链,而是检查函数定义时的外部环境,从当前作用域由内而外寻找最近的该变量的定义。动态作用域完全相反,变量的作用域是不确定的,根据函数的调用层次确定变量的定义,也就是说只要在调用函数之前在当前环境重新定义变量,函数就能使用新定义的变量,不关注结构上的层次。个人理解动态作用域的缺点就是变量随意覆盖可能引起意外bug,而且代码中一个变量有多个版本也会大大降低可读性吧。