# 闭包 ```python def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum >>> f = lazy_sum(1, 3, 5, 7, 9) >>> f .sum at 0x101c6ed90> ``` 注意到返回的函数在其定义内部引用了局部变量`args`,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了`f()`才执行。 # nonlocal 使用闭包,就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值,我们会发现返回的闭包函数调用一切正常。 ```python def inc(): x = 0 def fn(): # 仅读取x的值: return x + 1 return fn f = inc() print(f()) # 1 print(f()) # 1 ``` 但是,如果对外层变量赋值,由于Python解释器会把`x`当作函数`fn()`的局部变量,原因是`x`作为局部变量并没有初始化,直接计算`x+1`是不行的。但我们其实是想引用`inc()`函数内部的`x`,所以需要在`fn()`函数内部加一个`nonlocal x`的声明。加上这个声明后,解释器把`fn()`的`x`看作外层函数的局部变量,它已经被初始化了,可以正确计算`x+1`。 ```python def inc(): x = 0 def fn(): nonlocal x x = x + 1 return x return fn f = inc() print(f()) # 1 print(f()) # 2 ``` # 自定义装饰器 一般装饰器函数接受一个函数作为参数,并返回一个函数。这样就可以将装饰器函数@到要某一个函数的外层。 ```python from datetime import datetime # log()为一个装饰器函数 def log(func): def wrapper(*args, **kwargs): print("call %s(): " % func.__name__) return func(*args, **kwargs) return wrapper @log def now(): return datetime.now() #相当于执行log(now) ``` 一个完整的不带参数的装饰器 ```python import functools def log(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('call %s():' % func.__name__) return func(*args, **kwargs) return wrapper @log def function1(args1, args2): pass ``` 一个完整的带参数的装饰器 ```python import functools def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('%s %s():' % (text, func.__name__)) return func(*args, **kwargs) return wrapper return decorator @log(text) def function2(args1, args2): pass ``` # 内置装饰器property 在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改。这显然不合逻辑。为了限制score的范围,可以通过一个`set_score()`方法来设置成绩,再通过一个`get_score()`来获取成绩,这样,在`set_score()`方法里,就可以检查参数。 ```python class Student(object): def __init__(self): self._score = 0 def get_score(self): return self._score def set_score(self, value): if not isinstance(value, int): raise ValueError("score must be an integer!") if value < 0 or value > 100: raise ValueError("score must between 0~100!") self._score = value ``` ```python s = Student() s.set_score(60) s.get_score() 60 ``` 上面的调用方法又略显复杂,没有直接用属性这么直接简单。Python内置的`@property`装饰器就是负责把一个方法变成属性。 ```python class Student(object): @property def score(self): return self._score @score.setter def score(self, value): if not isinstance(value, int): raise ValueError("score must be an integer!") if value < 0 or value > 100: raise ValueError("score must between 0~100!") self._score = value ``` ```python s1 = Student() s1.score = 80 s1.score 80 ``` 还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性 ```python class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2021 - self._birth ``` 上面的`birth`是可读写属性,而`age`就是一个只读属性,因为`age`可以根据`birth`和当前时间计算出来。要特别注意:**属性的方法名不要和实例变量重名**。这是因为调用`s.birth`时,首先转换为方法调用,在执行`return self.birth`时,又视为访问`self`的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错`RecursionError`。