装饰器是一种设计模式,在Python中是一个非常有用的特性。可以在不修改函数、方法、类的情况下,修改(扩展)它们的行为。常见的装饰器如classmethod、staticmethod等,以@开始 加以使用,其实@只是Python提供的一个语法糖。
一个使用装饰器的例子:
1 2 3 4 class A (object ): @classmethod def count_all (cls ): pass
函数基础知识 要理解装饰器原理,需理解Python中的函数特性。在 Python 中,万物皆对象,变量名只是关联(引用)这些对象的标识符。函数也不例外,在Python中也是对象。与C++、Java等不同,函数在Python中是一等公民(First-class Citizen)。具有以下特性:
函数可以像变量一样,赋值给另一个变量
函数可作为参数传递给另一个函数
函数可作为另一个函数的返回值
函数里可以内嵌函数,并且内嵌函数可访问外层函数的变量
函数赋值 函数就像字符串(string)、整数(int)、列表(list)等可赋值给另一个变量。
1 2 3 4 5 6 7 8 9 10 11 def first (msg ): print(msg) first("Hello" ) second = first second("Hello" ) del firstsecond("Hello" ) first("Hello" )
输出:
1 2 3 4 Hello Hello Hello NameError: name 'first' is not defined
从输出结果可知,first 和 second 关联(引用)了同一个函数对象,当使用del删除first时,second仍可调用。
参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def inc (x ): return x + 1 def dec (x ): return x - 1 def operate (func, x ): result = func(x) return result print(operate(inc,3 )) print(operate(dec,3 ))
输出:
返回值 1 2 3 4 5 6 7 8 9 def is_called (): def is_returned (): print("Hello" ) return is_returned new = is_called() new()
输出:
嵌套函数 1 2 3 4 5 6 7 8 9 10 11 def dog (): height = 40 def profile (): print("I'm a dog and my height is {}." .format (height)) return profile if __name__ == "__main__" : dog_profile = dog() dog_profile()
输出:
1 I'm a dog and my height is 40.
内嵌函数profile 可以访问外层函数dog的局部变量height。height是闭包中的捕获变量(captured variable),捕获变量相互独立,互不影响。这是Python语言支持的特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def dog (): height = 40 def grow_up (): nonlocal height height = height + 1 def show_height (): print("Thanks for making me growing up. I'm now {} meters !!!!" .format (height)) return show_height return grow_up if __name__ == "__main__" : dog_1_grow_up = dog() dog_1_grow_up() dog_1_grow_up() dog_1_grow_up()() dog_2_grow_up = dog() dog_2_grow_up()()
装饰器实现 装饰器本身是一个函数,它的参数是一个函数并且返回一个函数,我们又把返回一个函数的函数称做高阶函数,所以装饰器就是一个高阶函数。
简单装饰器 一个简单的装饰器:
1 2 3 4 5 6 7 8 9 def make_pretty (func ): def inner (): print("I got decorated" ) func() return inner def ordinary (): print("I am ordinary" )
在shell中运行如下代码:
1 2 3 4 5 6 7 8 > >> ordinary() I am ordinary > >> > >> pretty = make_pretty(ordinary) > >> pretty() I got decorated I am ordinary
make_pretty 就是一个装饰器,ordinary被装饰器装饰。
@是Python提供的一个语法糖,代码:
1 2 3 @make_pretty def ordinary (): print("I am ordinary" )
与之等价的代码:
1 2 3 def ordinary (): print("I am ordinary" ) ordinary = make_pretty(ordinary)
问题:运行如下代码输出为:”inner”
1 print(ordinary.__name__)
使用装饰器之后,函数的名字等属性被改变了,可使用functools.wrap修正这个问题:
1 2 3 4 5 6 7 from functools import wrapsdef make_pretty (func ): @wraps(func ) def inner (): print("I got decorated" ) func() return inner
被装饰函数带有参数 上面实现的装饰器只能装饰无参数的函数,为实现一个可装饰带任意参数的函数的装饰器,可使用*args和**kwargs。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from functools import wrapsdef make_pretty (func ): @wraps(func ) def inner (*args, **kwargs ): print("I got decorated" ) return func(*args, **kwargs) return inner @make_pretty def ordinary (): print("I am ordinary" ) @make_pretty def divide (a, b ): print(a/b) return a / b
在shell中运行代码:
1 2 3 4 5 6 > >> print (divide.__name__) divide > >> print (divide(4,2)) I got decorated 2.0 2.0
装饰器如何带参数? 装饰器可通过参数控制其行为,比如实现一个限制调用次数的装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from functools import wrapsdef limit_query (limit ): def decorator (func ): @wraps(func ) def wrapper (*args, **kwargs ): if wrapper.count < limit: wrapper.count += 1 return func(*args, **kwargs) else : print(f'No queries left. All {wrapper.count} queries used.' ) return "No queries left" wrapper.count = 0 return wrapper return decorator @limit_query(3 ) def echo (value ): return f"{value} " if __name__ == "__main__" : print(echo(1 )) print(echo(2 )) print(echo(3 )) print(echo(4 ))
输出:
1 2 3 4 5 1 2 3 No queries left. All 3 queries used. No queries left
装饰器可以是类! 使用类也可以实现一个装饰器,通过实现__call__方法,是类符合可调用(callable)对象的要求即可。函数调用次数统计实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import functoolsclass CountCalls : def __init__ (self, func ): functools.update_wrapper(self, func) self.func = func self.num_calls = 0 def __call__ (self, *args, **kwargs ): self.num_calls += 1 print(f"Call {self.num_calls} of {self.func.__name__!r} " ) return self.func(*args, **kwargs) @CountCalls def say_whee (): print("Whee!" ) if __name__ == "__main__" : print(say_whee.__name__) say_whee() say_whee()
输出:
1 2 3 4 5 say_whee Call 1 of 'say_whee' Whee! Call 2 of 'say_whee' Whee!
可这样理解:
1 2 3 say_whee = CountCalls(say_whee) say_whee() say_whee()
装饰器本身带参数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class LimitQuery : def __init__ (self, limit ): print("[LimitQuery]__init__" ) self.limit = limit self.count = 0 def __call__ (self, func ): print("[LimitQuery]__call__" ) @wraps(func ) def wrapper (*args, **kwargs ): if self.count < self.limit: self.count += 1 return func(*args, **kwargs) else : print(f'No queries left. All {self.count} queries used.' ) return "No queries left" return wrapper @LimitQuery(limit=3 ) def get_coin_price (value ): return f"{value} " if __name__ == "__main__" : print(get_coin_price(1 )) print(get_coin_price(2 )) print(get_coin_price(3 )) print(get_coin_price(4 ))
输出:
1 2 3 4 5 6 7 [LimitQuery]__init__ [LimitQuery]__call__ 1 2 3 No queries left. All 3 queries used. No queries left
可这样理解:
1 2 3 limit_query = LimitQuery(3 ) get_coin_price = limit_query(get_coin_price) get_coin_price(1 )
装饰器的有序性 一个函数可被多个装饰器装饰,装饰顺序从下到上,即从靠近函数的装饰器开始。代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def star (func ): def inner (*args, **kwargs ): print("*" * 30 ) func(*args, **kwargs) print("*" * 30 ) return inner def percent (func ): def inner (*args, **kwargs ): print("%" * 30 ) func(*args, **kwargs) print("%" * 30 ) return inner @star @percent def printer (msg ): print(msg) printer("Hello" )
输出:
1 2 3 4 5 ****************************** % %%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello % %%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************
可以这样理解:
1 star(percent(printer))("Hello" )
实用的装饰器 @call_counter 统计函数调用次数
1 2 3 4 5 6 7 def call_counter (func ): def helper (*args, **kwargs ): helper.calls += 1 return func(*args, **kwargs) helper.calls = 0 return helper
@dataclass 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from dataclasses import dataclass@dataclass class InventoryItem : """Class for keeping track of an item in inventory.""" name: str = "" unit_price: float = 1.0 quantity_on_hand: int = 0 def total_cost (self ) -> float: return self.unit_price * self.quantity_on_hand if __name__ == "__main__" : obj = InventoryItem("test" ,2.0 , 3 ) print(obj.__dict__)
输出:
1 {'name': 'test', 'unit_price': 2.0, 'quantity_on_hand': 3}
dataclass 自动为类添加特殊函数和成员变量。
@singleton 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def singleton (cls ): instances = {} def wrapper (*args, **kwargs ): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singleton class A : def __init__ (self, name ): self.name = name if __name__ == "__main__" : a = A('hello' ) b = A('world' ) print(a) print(b) print(a.name) print(b.name) print(type (A))
输出:
1 2 3 4 5 <__main__.A object at 0x1020e2f70> <__main__.A object at 0x1020e2f70> hello hello <class 'function'>
从输出结果可以看到,a和b代表同一个对象(类A的实例),经过singleton装饰之后,类A的类型变成了函数。
参考资料