前面写过一篇《原来这就是python装饰器》,主要介绍蛇什么是装饰器,这一篇主要介绍装饰器所运用到的技术原理,最终说明装饰器的工作原理!
如下的例子:
b = 6
def f1(a):
print(a)
print(b)
b = 9
print(b)
if __name__ == "__main__":
f1(3)
上例中,我们的程序会输出3并报错:UnboundLocalError: local variable 'b' referenced before assignment。这是因为python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。
想要解决上面的报错问题,需要将变量b声明为global,也就是全局变量。
b = 6
def f1(a):
global b
print(a)
print(b)
b = 9
print(b)
if __name__ == "__main__":
f1(3)
此时顺序输出3、6、9。
registry = []
def register(func):
print('running register {}'.format(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('runing main()')
print('registry -> ', registry)
f1()
f2()
f3()
if __name__ == "__main__":
main()
我们看看上例的输出。
running register
running register
runing main()
registry -> [, ]
running f1()
running f2()
running f3()
我们看到,在main函数执行之前,就有两行输出。这说明装饰器在加载模块时立即执行,而被装饰的函数只在明确调用时运行。并且我们要知道装饰器能把被装饰的函数替换成其他函数。
我们还是直接上代码来说明什么时闭包?
def make_averager():
series = []
def averager(value):
series.append(value)
total = sum(series)
return total/len(series)
return averager
if __name__ == "__main__":
avg = make_averager()
print(avg(5))
print(avg(10))
print(avg(15))
上述程序的输出依次是5、7.5、10,上面的make_averager()函数就形成了一个闭包。
所谓闭包就是在一个外函数中定义一个内函数,此例中,make_averager()就是外函数,averager()就是内涵数,在内函数中运用了外函数的临时变量,并且外函数的返回值是内涵数的引用,这样就构成了一个闭包。而这个临时变量就会series,返回就是averager就是内函数的引用。
对于一个函数,如果函数名后面紧跟一个括号,相当于现在我就要调用这个函数;如果不跟括号,相当于只是一个函数的名字,这个时候里面存放的是函数所在的位置的引用。
在上面这个闭包函数的实现中,我们利用可变对象series来实现变量的访问。如果这个变量是数字、字符串、元组等不可变类型,只能读取,不能更新,这是我们将无法在内函数中访问外函数中定义的变量。如何解决呢?
在python3中,我们引入了nonlocal声明来解决变量不能访问的问题!
def make_averager():
series = 0
count = 0
def averager(value):
nonlocal series,count
series += value
count += 1
return series/count
return averager
此时将series定义为nonlocal,就可以实现正常的访问了!这个时候有一个问题,此时我们能不能将series定义为global呢?如果我们将series定义为global类型,就会报错:NameError: name 'series' is not defined。这是为什么?还是没有弄懂,一个坑,如果有谁看到这篇博客,并且知道原因,请留言告诉我,万分感谢!不过我们将程序改为下面的样子,可以输出正确值!
def make_averager():
global series, count
series = 0
count = 0
def averager(value):
global series,count
series += value
count += 1
return series/count
return averager
下面来说说global和nonlocal的区别!global表示将变量声明为全局变量;nonlocal表示将变量声明为外层变量(外层函数的局部变量,而且不能是全局变量)。
def logged(func):
def with_logging(*args,**kwargs):
print(func.__name__ + "was callled")
return func(*args,**kwargs)
return with_logging
@logged
def f(x):
return x + x*x
f = logged(f)
print(f.__name__)
如上例所示,在被装饰器修饰之后,这个函数的输出为:with_logging,没有它原本的功能信息。这个时候,我们利用functools.wraps可是实现将原函数对象的指定属性复制给包装函数对象。
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args,**kwargs):
print(func.__name__ + "was callled")
return func(*args,**kwargs)
return with_logging
@logged
def f(x):
return x + x*x
f = logged(f)
print(f.__name__)
#输出为f
property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
class LineItem:
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
def get_weight(self):
return self.__weight
def set_weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError("value must be > 0")
weight = property(get_weight, set_weight)
上例中我们不使用装饰器,写一个简单的计价程序。如下使用装饰器,就是下面的样子。
class LineItem:
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
@property
def weight(self):
return self.__weight
@property
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError("value must be > 0")
可以看到,相对于第一种写法,第二种要简单!
我们知道对于一个普通的类,我们要使用其中的函数的话,需要对类进行实例化;而一个类中,某个函数前面加上了staticmethod或者classmethod的话,那么这个函数就可以不通过实例化直接调用。
这两个方法本身有什么区别呢?
<1>@staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
<2>@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。
<3>如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。
<4>而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。
简单来说,就是staticmethod不需要传入表示自身对象参数,而classmethod需要传入一个类似于self的cls参数。那么既然classmethod麻烦一点,必然有他麻烦的道理,就是:使用classmethod的话,凡是在该类中的类的属性,方法,实例化对象等,都可以调用出来。