1. 函数装饰器的工作原理
装饰器(fuctional decorators)用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能。
这个函数的特殊之处在于它的返回值也是一个函数,这个函数是内嵌“原”函数的函数。
本质上,decorator 就是一个返回函数的高阶函数。装饰器函数在被装饰函数定义好后立即执行。
1)最原始的装饰器
以下面这个例子来讲解装饰器是怎么运行的:
被装饰器装饰的函数 f 即使没有被调用,但因为被 @deco 修饰,所以装饰器的代码已经运行了,即函数 deco 已经运行了,deco内部嵌套的 wrapper 显然还没有
运行,只是被定义了下,然后返回 wrapper 函数名变量。执行流程如下:
a. 先定义原函数 f。
b. 然后运行装饰器函数,使得被装饰的原函数 f 重新引用了装饰器内部的那个函数名 wrapper。
import time
def deco(f):
def wrapper():
start_time = time.time()
f()
end_time = time.time()
execution_time = (end_time - start_time) * 1000
print("time is %d ms" % execution_time )
return wrapper
@deco # 此行代码等同于,f = deco(f) = wrapper
def f():
time.sleep(1)
f() # f() = wrapper()
"""
output:
time is 1015 ms
"""
2)带有固定参数的装饰器
由1)中的解释,我们可以知道调用f时,实际调用的是wrapper,参数先通过wrapper的形参传入,然后wrapper内部再调用函数f,参数再间接传给f。
import time def deco(f): def wrapper(a,b): start_time = time.time() f(a,b) end_time = time.time() execution_time = (end_time - start_time) * 1000 print("time is %d ms" % execution_time) return wrapper @deco def f(a,b): time.sleep(1) f(3,4) # f = wrapper -> f(3,4) = wrapper(3,4)
3)带有可变参数的装饰器
如果可变参数语法不理解请先阅读另一篇博客 默认参数和可变参数
import time def deco(f): def wrapper(*args, **kwargs): # args = (3,4), kwargs = {} start_time = time.time() # args 和 kwargs 分别被解包后,再整体按顺序赋值给 f 的形参 f(*args, **kwargs) end_time = time.time() execution_time = (end_time - start_time)*1000 print("time is %d ms" % execution_time) return wrapper @deco def f(a,b): time.sleep(1) f(3,4)
4)使用多个装饰器,装饰一个函数
多个装饰器装饰的顺序是从里到外,也可以说由近到远,直接来看一个例子吧。
def deco01(f): def wrapper(*args, **kwargs): print("this is deco01") f(*args, **kwargs) print("deco01 end here") return wrapper def deco02(f): def wrapper(*args, **kwargs): print("this is deco02") f(*args, **kwargs) print("deco02 end here") return wrapper @deco01 @deco02 def f(a,b): print("我是被装饰的函数") f(3,4) """ output: this is deco01 this is deco02 我是被装饰的函数 deco02 end here deco01 end here """
按由近到远的原则,首先先装饰deco02,便得到下面的函数体:
print("this is deco02") f(*args, **kwargs) print("deco02 end here")
然后继续装饰deco01,在已经装饰了deco02的基础上,继续扩展代码,函数体就变成这样:
print("this is deco01") print("this is deco02") f(*args, **kwargs) print("deco02 end here") print("deco01 end here")
给个图,一目了然:
2. python内置的函数装饰器
有3种,分别是 @staticmethod、@classmethod 和 @property。
其中 staticmethod()、classmethod() 和 property() 都是 Python 的内置函数。
- 先解释下python中的类变量(或叫做静态变量),它定义在类中的位置如下:
class Test: stc_attr = 1 # 类变量 def __init__(self,attr1,attr2): self.attr1 = attr1 self.attr2 = attr2
1)staticmethod 是类的静态方法:其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用,也可以在实例化的情况使用。
由于静态方法不包含 self 参数,所以它只能通过类名访问类的静态成员变量和静态成员函数,如 类名.属性名、类名.方法名。
注:如果不通过类名访问静态成员变量,那其实是重新定义了一个变量。不通过类名访问静态成员函数,会提示函数无定义。
class Test(object):
x = 0.1
@staticmethod
def f():
print("call static method.")
Test.x = 0.5
Test.f() # 静态方法无需实例化
Test().f() # 也可以实例化后调用
print(Test.x)
"""
output:
call static method.
call static method.
0.5
"""
2)classmethod 是类方法:与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型,有子类继承时,就是子类类型)。
其使用语法和staticmethod方法一致。但静态方法的行为就是一个普通的全局函数,而类方法包含cls参数,那cls参数有啥用呢?
解释:比如静态方法想要调用非静态的成员,必须知道类的具体类型,然后在函数内部构造一个实例来调用,在存在派生类的代码中,知道具体类型还挺麻烦,如果
类名被修改,那代码就也得改。但 classmethod 方法却可以直接知道类的具体类型,即cls。看一个例子便清楚了:
class Test(object): a = 123 def normalFoo(self): print('call normalFoo.') @staticmethod def staticFoo(): print('call staticFoo.') print(Test.a) Test().normalFoo() # 访问非静态成员 @classmethod def classFoo(cls): print('call classFoo.') print(Test.a) cls().normalFoo() # 访问非静态成员 Test.staticFoo() Test.classFoo()
3)property:把一个方法变成属性调用,起到既能检查属性,还能用属性的方式来访问该属性。
a. 为什么需要它呢?我们在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把属性随便改:
class Student(object): def __init__(self, score = 0): self.score = score s = Student() s.score = 100 s.score = 200 # 分数为200明显不合理 s.score = -50 # 分数为负数也不合理 print(s.score)
b. 对值进行约束的一个明显解决方案是隐藏属性score(使其私有)并定义新的 getter 和 setter 接口来操作它。可以按照下面这样改:
class Student(object): def __init__(self, value=0): self.setScore(value) def getScore(self): return self._score def setScore(self, value): if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value s = Student() s.setScore(100) s.setScore(105) # 报错 s.setScore(-50) # 报错 print(s.getScore())
请注意,Python中不存在私有变量,但还是有一些简单的准则可以遵循。Python语言本身并不会做限制。这次更新虽然成功地实现了新的限制,
但存在的一个大问题是,所有在其程序中实现我们前面的类的客户都必须修改他们的代码,将 s.score 修改为 s.getScore(),并且将像 s.score= val
的所有赋值语句修改为 s.setScore(val)。这种重构可能会给客户带来数十多万行代码的麻烦。
c. 这时property就派上用场了。@property真正强大的就是可以对属性增加约束来限制属性的定义。
class Student(object): def __init__(self, value=0): self._score = value @property # 以需要定义的属性为方法名,如果没有@属性名.setter,则就是一个只读属性 def score(self): return self._score @score.setter # @property定义可访问属性的语法:以属性名为方法名,并在方法名上增加@属性名.setter def score(self, value): if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value s = Student() s.score = 100 # s.score = 105 # 报错 s.score = -50 # 报错 print(s.score)
这样就可以像普通属性那样操作,又可以进行约束。