装饰器的本质就是函数, 可以让其他函数在不需要做任何代码变动的前提下增加额外的功能
装饰器的返回值也是一个函数对象
常用于: 插入日志,性能测试,事务处理,缓存,权限校验等
不能修改被装饰的函数的源代码
不能修改被装饰函数的调用方式
def deco(func): print('start func') return func # 这里如果没有return就继续执行了 print('end func') def test(): print('this is test') test = deco(test) test() >>> start func this is test
test = deco(test)
等价于
@deco def test(): pass
def deco(func): def inside(): print('start func') func() # 调用的时候真正调用的是test函数 print('end func') return inside def test(): print('this is test') test = deco(test) test() >>> start func this is test end func
def deco(func): def inside(): print('start func') func() print('end func') return inside @deco def test(): print('this is test') test() >>> start func this is test end func
def deco(func): def inside_deco(a, b): print('inside start') ret = func(a, b) print('inside end') return ret #这是返回func的关键 return inside_deco @deco def myfunc(a, b): print('value a %s, b %s' % (a, b)) return a + b ret = myfunc(1, 2) print('ret value', ret) >>> inside start value a 1, b 2 inside end ret value 3
def deco(func): def inside_deco(*args, **kwargs): print('inside start') ret = func(*args, **kwargs) print('inside end') return ret return inside_deco @deco def myfunc(*args, **kwargs): print('args: %s, kwargs: %s' % (args, kwargs)) return args ret = myfunc(1, 2, 3, a=1, b=2, c=3) print('ret value', ret) >>> inside start args: (1, 2, 3), kwargs: {'b': 2, 'a': 1, 'c': 3} inside end ret value (1, 2, 3)
def outside_deco(deco_params): def deco(func): def inside_deco(*args, **kwargs): print('inside start with %s' % (deco_params)) ret = func(*args, **kwargs) print('inside end with %s' % (deco_params)) return ret return inside_deco return deco @outside_deco('deco params') def myfunc(*args, **kwargs): print('args: %s, kwargs: %s' % (args, kwargs)) return args ret = myfunc(1, 2, 3, a=1, b=2, c=3) print('ret value', ret) >>> inside start with deco params args: (1, 2, 3), kwargs: {'b': 2, 'a': 1, 'c': 3} inside end with deco params ret value (1, 2, 3)
如果一个函数被多个装饰器修饰,其实应该是该函数先被最里面的装饰器修饰, 即从里到外执行
@deco1 @deco2 @deco3 def func() pass 等价于 deco1(deco2(deco3(func)))
def outer(func): print('Enter outer', func) def wrapper(): print('Running outer') func() return wrapper def inner(func): print('Enter inner', func) def wrapper(): print('Running inner') func() return wrapper @outer @inner def main(): print('Running main') if __name__ == "__main__": main() >>> Enter inner <function main at 0x10a1ae730> Enter outer <function inner.<locals>.wrapper at 0x10a1ae7b8> Running outer Running inner Running main
函数main()先被inner装饰,变成新的函数,变成另一个函数后,再次被装饰器修饰
一个装饰器可以接收一个类,并返回一个类,起到加工的作用。
def deco(aClass): class newClass: def __init__(self, age): self.total_display = 0 self.wrapper = aClass(age) def display(self): self.total_display += 1 print('total display', self.total_display) self.wrapper.display() return newClass @deco class Bird: def __init__(self, age): self.age = age def display(self): print('My age: ', self.age) eagleLord = Bird(5) for i in range(3): eagleLord.display() >>> total display 1 My age: 5 total display 2 My age: 5 total display 3 My age: 5
类装饰器主要依赖于函数__call__()
, 每当调用一个类实例,函数__call__()
就会被执行一次
class Count: def __init__(self, func): self.func = func self.num_calls = 0 def __call__(self, *args, **kwargs): self.num_calls += 1 print('Num of calls is : {}'.format(self.num_calls)) return self.func(*args, **kwargs) @Count def example(): print('hello world') example() example() >>> Num of calls is : 1 hello world Num of calls is : 2 hello world
在函数语句块的第一行书写,习惯使用三个引号封装
使用特殊属性__doc__
访问这个文档
In [7]: def add(x, y): ...: """This is doc string""" ...: a = x +y ...: return a ...: In [8]: "name={}\ndoc={}".format(add.__name__, add.__doc__) Out[8]: 'name=add\ndoc=This is doc string'
静态方法并不能访问私有变量,只是给类方法加了类的属性
静态函数可以用来做一些简单独立的任务,既方便测试,也能优化代码结构
即和类以及对象都没有关系, 和普通的函数没有太多区别,常用于和类对象无任何关系的时候使用,即会使用到当前类里面的逻辑
会使用到当前类里面的逻辑
class A: __val = 3 @staticmethod def print_val(): print(__val) a = A() a.print_val() A.print_val() >>> Traceback (most recent call last): File "/Users/xhxu/python/python3/test/9.py", line 10, in <module> a.print_val() File "/Users/xhxu/python/python3/test/9.py", line 6, in print_val print(__val) NameError: name '_A__val' is not defined
静态方法只属于定义他的类,而不属于任何一个具体的对象, 即不能传self,相当于单纯的一个函数
静态方法无需传入self参数,因此在静态方法中无法访问实例变量
静态方法不能直接访问类的静态变量,但可以通过类名引用静态变量
与成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用。
静态方法和类方法都是通过给类发消息来调用的
class MyClass: var1 = "String1" @staticmethod #静态方法 def staticmd(): print('I am static method') MyClass.staticmd() #调用了静态方法 c = MyClass() c.staticmd() >>> I am static method I am static method
至少一个cls参数
执行类方法时,自动将调用该方法的类复制给cls, cls指代调用者即类对象自身,通过这个cls参数我们可以获取和类相关的信息并且可以创建出类的对象
类方法传递类本身, 可以访问私有的类变量
类函数最常用的功能是实现不同的 init 构造函数
class A: __val = 3 @classmethod def get_val(cls): return cls.__val a = A() print(a.get_val()) print(A.get_val()) >>> 3 3
与静态方法一样,类方法可以使用类名调用类方法
与静态方法一样,类成员方法也无法访问实例变量,但可以访问类的静态变量
与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)。
class MyClass: val1 = "String1" #静态变量 def __init__(self): self.val2 = "Value 2" @classmethod #类方法 def classmd(cls): print('Class: ' + str(cls) + ',val1: ' + cls.val1 + ', Cannot access value 2') MyClass.classmd() c = MyClass() c.classmd() >>> Class: <class '__main__.MyClass'>,val1: String1, Cannot access value 2 Class: <class '__main__.MyClass'>,val1: String1, Cannot access value 2
from time import time, localtime, sleep class Clock(object): """Digital clock """ def __init__(self, hour=0, minute=0, second=0): self._hour = hour self._minute = minute self._second = second @classmethod def now(cls): ctime = localtime(time()) return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec) def run(self): self._second += 1 if self._second == 60: self._second = 0 self._minute += 1 if self._minute == 60: self._minute = 0 self._hour += 1 if self._hour == 24: self._hour = 0 def show(self): return '%02d:%02d:%02d' % \ (self._hour, self._minute, self._second) def main(): # 通过类方法创建对象并获取系统时间 clock = Clock.now() while True: print(clock.show()) sleep(1) clock.run() if __name__ == "__main__": main()
类方法不能访问实例变量, 但可以访问类变量
class A: __val = 3 def __init__(self, x): self.x = x @classmethod def get_val(cls): print(cls.x) return cls.__val a = A(5) print(A.get_val()) >>> Traceback (most recent call last): File "/Users/xhxu/python/python3/test/9.py", line 12, in <module> print(A.get_val()) File "/Users/xhxu/python/python3/test/9.py", line 9, in get_val print(cls.x) AttributeError: type object 'A' has no attribute 'x'
加上property 装饰器,在函数调用的时候,就不需要加上小括号
property调用的时候,只能调用self参数
将一个类方法转变成一个静态属性,只读属性。
property 属性必须在setter 和 deleter之前
类属性可以直接被类调用,如果想要实现能被类直接调用的方法就可以借助staticmethod和classmethod了,区别在于staticmethod的方法没有self参数,通常用来直接定一个静态类方法,如果想将一个普通动态方法变成类方法就要使用classmethod了。
class A: def __init__(self): self.__value = 0 @property # property 可以访问私有的变量 def value(self): if self.__value < 0: return 0 return self.__value @value.setter # 加了这个装饰器,才会被认为是属性 def value(self, val): if isinstance(val, (int, float)) and val >= 0: self.__value = val else: self.__value = 0 a = A() a.value = -1 print(a.value) a.value = 3 print(a.value) >>> 0 3
同一个对象的不同属性之间可能存在依赖关系。当某个属性被修改时,依赖于该属性的其他属性也会同时变化。
class bird(object): feather = True class chicken(bird): fly = False def __init__(self, age): self.age = age def getAdult(self): if self.age > 1.0: return True else: return False adult = property(getAdult) # 这里定义了adult变量,其等同于将getAdult方法添加@property装饰器的结果指向给adult summer = chicken(2) print(summer.adult) summer.age = 0.5 print(summer.adult) >>> True False
from abc import ABCMeta, abstractmethod class Employee(object, metaclass=ABCMeta): """Employee """ def __init__(self, name): self._name = name @property def name(self): return self._name @abstractmethod def get_salary(self): pass class Manager(Employee): def get_salary(self): return 15000.00 class Programmer(Employee): def __init__(self, name, working_hour=0): super().__init__(name) self._working_hour = working_hour @property def working_hour(self): return self._working_hour @working_hour.setter def working_hour(self, working_hour): self._working_hour = working_hour if working_hour > 0 else 0 def get_salary(self): return 150.00* self._working_hour class Salesman(Employee): def __init__(self, name, sales=0): super().__init__(name) self._sales = sales @property def sales(self): return self._sales @sales.setter def sales(self, sales): self._sales = sales if sales > 0 else 0 def get_salary(self): return 1200.00 + self._sales * 0.05 def main(): emps = [Manager('William'), Programmer('Rick'), Salesman('Frank')] for emp in emps: if isinstance(emp, Programmer): emp.working_hour = int(input('Input %s working hours: ' % emp.name)) elif isinstance(emp, Salesman): emp.sales = float(input('Input %s sales rate: ' % emp.name)) print('%s monthly pay check: %s' % (emp.name, emp.get_salary())) if __name__ == "__main__": main()
与属性名同名,并且接受2个参数,第一个是self, 第二个是将要赋值的值。
class A: def __init__(self): self.__val = 0 @property def val(self): if self.__val < 0: return 0 return self.__val @val.setter def val(self, value): if isinstance(value, (int, float)) and value >= 0: self.__val = value else: self.__val = 0 a = A() print(a.val) a.val = 3 print(a.val) >>> 0 3
可以控制是否删除属性,不常用
class Person: def __init__(self, name, age=18): self.name = name self.__age = age @property def age(self): return self.__age @age.setter def age(self, age): self.__age = age @age.deleter def age(self): print('del age') p = Person('Rick') print(p.age) p.age = 30 print(p.age) del p.age print(p.age) >>> 18 30 del age 30
property()最多可以加载四个参数。
第一个参数是方法名,调用 对象.属性 时自动触发执行方法
第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
第四个参数是字符串,调用 对象.属性.__doc__
,此参数是该属性的描述信息
negative 为一个特性,用于表示数字的负数。property() 最后一个参数('I am negative one') 表示特性negative的说明文档
class Goods(object): def __init__(self): self.original_price = 100 self.discount = 0.8 def get_price(self): new_price = self.original_price * self.discount return new_price def set_price(self, value): self.original_price = value def del_price(self): del self.original_price PRICE = property(get_price, set_price, del_price, 'Price description') obj = Goods() print(obj.PRICE) obj.PRICE = 200 print(obj.PRICE) del obj.PRICE >>> 80.0 160.0
class A: def __init__(self): self.__val = 0 def get_val(self): return self.__val def set_val(self, value): self.__val = value val = property(get_val, set_val) print(type(A.val)) a = A() a.val = 3 print(a.val) >>> <class 'property'> 3
使用装饰器会产生我们可能不希望出现的副作用, 例如:改变被修饰函数名称,对于调试器或者对象序列化器等需要使用内省机制的那些工具,可能会无法正常运行;其实调用装饰器后,会将同一个作用域中原来函数同名的那个变量(例如下面的func_1),重新赋值为装饰器返回的对象;使用@wraps后,会把与内部函数(被修饰函数,例如下面的func_1)相关的重要元数据全部复制到外围函数
这里函数的元信息改变了
def my_decorator(func): def wrapper(*args, **kwargs): print('Wrapper of decorator') func(*args, **kwargs) return wrapper @my_decorator def greet(message): print(message) greet('hello world') print(greet.__name__) >>> Wrapper of decorator hello world wrapper
使用内置的wraps 保留原函数的元信息
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print('Wrapper of decorator') func(*args, **kwargs) return wrapper @my_decorator def greet(message): print(message) greet('hello world') print(greet.__name__) >>> Wrapper of decorator hello world greet
from functools import wraps def authenticate(func): @wraps(func) def wrapper(*args, **kwargs): request = args[0] if check_user_logged_in(request): return func(*args, **kwargs) else: raise Exception('Authentication failed') return wrapper @authenticate def post_message(request, ...): pass
在实际工作中,如果你怀疑某些函数的耗时过长,导致整个系统的latency(延迟)增加,所以想在线上测试某些函数的执行时间,那么,装饰器就是一种很常用的手段。
import time import functools def log_executed_time(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() res = func(*args, **kwargs) end = time.perf_counter() print('{} took {} ms'.format(func.__name__, (end - start) * 1000)) return res return wrapper @log_executed_time def calculate_similarity(items) pass
在大型公司的机器学习框架中,调用机器集群进行模型训练前,往往会用装饰器对其输入(往往是很长的json文件)进行合理性检查。这样就可以大大避免,输入不正确对机器造成的巨大开销。
import functools def validation_check(input): @functools.wraps(func) def wrapper(*args, **kwargs): ... # 检查输入是否合法 @validation_check def neural_network_training(param1, param2, ...): ...
关于缓存装饰器的用法,其实十分常见,以Python内置的LRU cache为例来说明
LRU cache,在Python中的表示形式是@lru_cache。@lru_cache会缓存进程中的函数参数和结果,当缓 存满了以后,会删除least recenly used 的数据。
正确使用缓存装饰器,往往能极大地提高程序运行效率。为什么呢?我举一个常见的例子来说明。
大型公司服务器端的代码中往往存在很多关于设备的检查,比如你使用的设备是安卓还是iPhone,版本号 是多少。这其中的一个原因,就是一些新的feature,往往只在某些特定的手机系统或版本上才有(比如 Android v200+)。
这样一来,我们通常使用缓存装饰器,来包裹这些检查函数,避免其被反复调用,进而提高程序运行效率,
比如写成下面这样
@lru_cache def check(param1, param2): # 检查用戶设备类型,版本号等等 pass