Python进阶: 通过实例详解装饰器
Latrice2344
8年前
<p style="text-align:center"><img src="https://simg.open-open.com/show/a4c09d6a84950dbf7d3f7b51393ac91a.png"></p> <p>Python中的装饰器有很多用处,比如输出日志、参数检查、代理设置、计数计时、结果缓存等等。本文就通过几个装饰器例子,详细解释一下Python中装饰器的用法。</p> <ul> <li> <p>一步步从简到繁学习装饰器用法</p> </li> <li> <p>其他一些装饰器实例</p> </li> <li> <p>Python中自带的装饰器</p> </li> </ul> <h2><strong>一步步从简到繁学习装饰器用法</strong></h2> <p>(1)最简单的装饰器,实现日志输出功能:</p> <pre> <code class="language-python"># 构建装饰器 def logging(func): @functools.wraps(func) def decorator(): print("%s called" % func.__name__) result = func() print("%s end" % func.__name__) return result return decorator # 使用装饰器 @logging def test01(): return 1 # 测试用例 print(test01()) print(test01.__name__)</code></pre> <p>代码很简单,很容易看懂。这里注意"functools.wraps"用法,其目的是"test01.__name__"输出正确的"test01"。"@logging"相当于"test01 = logging(test01)",返回的是decorator函数,所以如果不加"functools.wraps",则"test01.__name__"返回为"decorator"。</p> <p>注意,此时test01没有参数,对于带有参数的函数,logging装饰器则不再适用。那么如果想装饰带有参数的函数,装饰器该怎么写呢?</p> <p>(2)装饰器传入函数参数,并正确返回结果:</p> <pre> <code class="language-python"># 构建装饰器 def logging(func): @functools.wraps(func) def decorator(a, b): print("%s called" % func.__name__) result = func(a, b) print("%s end" % func.__name__) return result return decorator # 使用装饰器 @logging def test01(a, b): print("in function test01, a=%s, b=%s" % (a, b)) return 1 # 测试用例 print(test01(1, 2))</code></pre> <p>这里的test01函数带有参数a、b,那么decorator函数带有同样的参数即可。那么问题又来了,如何让logging装饰器更加通用,而不是只装饰参数为两个的函数呢?这时候自然想到Python中的 * 和 ** 的用法。</p> <pre> <code class="language-python"># 构建装饰器 def logging(func): @functools.wraps(func) def decorator(*args, **kwargs): print("%s called" % func.__name__) result = func(*args, **kwargs) print("%s end" % func.__name__) return result return decorator # 使用装饰器 @logging def test01(a, b): print("in function test01, a=%s, b=%s" % (a, b)) return 1 # 使用装饰器 @logging def test02(a, b, c=1): print("in function test02, a=%s, b=%s, c=%s" % (a, b, c)) return 1 # 测试用例 print(test01(1, 2)) print(test02(1, 2, c=3, d=4))</code></pre> <p>此时对于任意参数的函数,logging都可以进行装饰。但是注意,logging装饰器是不带参数的,那么装饰器可以带参数吗?当然可以,我们换个例子:参数检查。</p> <p>(3)构建带有参数的装饰器,并正确返回结果:</p> <pre> <code class="language-python"># 构建装饰器 def params_chack(a_type, b_type): def _outer(func): @functools.wraps(func) def _inner(a, b): assert isinstance(a, a_type) and isinstance(b, b_type) return func(a, b) return _inner return _outer # 使用装饰器 @params_chack(int, (list, tuple)) def test03(a, b): print("in function test03, a=%s, b=%s" % (a, b)) return 1 # 测试用例 print(test03(1, [2, 3])) # 参数正确 print(test03(1, 2)) # 参数错误</code></pre> <p>从代码可以看出,实际上就是在原有装饰器的基础上,外层又加了一层包装。params_check装饰器的作用是限制第一个参数为a_type,第二个参数为b_type。类似于(2),这里如何让装饰器更加通用,而不是只装饰参数为两个的函数呢?这里又一次想到Python中的 * 和 **。</p> <pre> <code class="language-python"># 构建装饰器 def params_chack(*types, **kwtypes): def _outer(func): @functools.wraps(func) def _inner(*args, **kwargs): result = [isinstance(_param, _type) for _param, _type in zip(args, types)] assert all(result), "params_chack: invalid parameters" result = [isinstance(kwargs[_param], kwtypes[_param]) for _param in kwargs if _param in kwtypes] assert all(result), "params_chack: invalid parameters" return func(*args, **kwargs) return _inner return _outer # 使用装饰器 @params_chack(int, str, c=(int, str)) def test04(a, b, c): print("in function test04, a=%s, b=%s, c=%s" % (a, b, c)) return 1 # 测试用例 print(test04(1, "str", 1)) # 参数正确 print(test04(1, "str", "abc")) # 参数正确 print(test04("str", 1, "abc")) # 参数错误</code></pre> <p>此时params_check装饰器不但能够传入任意个数的参数,而且支持K-V形式的参数传递。</p> <p>(4)使用装饰器装饰类中的函数,比较简单,直接看代码。注意此时第一个参数为self本身:</p> <pre> <code class="language-python"># 使用装饰器 class ATest(object): @params_chack(object, int, str) def test(self, a, b): print("in function test of ATest, a=%s, b=%s" % (a, b)) return 1 # 测试用例 a_test = ATest() a_test.test(1, "str") # 参数正确 a_test.test("str", 1) # 参数错误</code></pre> <p>(5)多个装饰器同时装饰一个函数,也比较简单,直接看代码:</p> <pre> <code class="language-python"># 使用装饰器 @logging @params_chack(int, str, (list, tuple)) def test05(a, b, c): print("in function test05, a=%s, b=%s, c=%s" % (a, b, c)) return 1 # 测试用例 print(test05(1, "str", [1, 2])) # 参数正确 print(test05(1, "str", (1, 2))) # 参数正确 print(test05(1, "str", "str1str2")) # 参数错误</code></pre> <p>(6)将装饰器写为类的形式,即“装饰器类”。此时对于装饰器类的要求是必须是可被调用的,即必须实现类的__call__方法。直接上代码:</p> <pre> <code class="language-python"># 构建装饰器类 class Decorator(object): def __init__(self, func): self.func = func return def __call__(self, *args, **kwargs): print("%s called" % self.func.__name__) result = self.func(*args, **kwargs) print("%s end" % self.func.__name__) return result # 使用装饰器 @Decorator def test06(a, b, c): print("in function test06, a=%s, b=%s, c=%s" % (a, b, c)) return 1 # 测试用例 print(test06(1, 2, 3))</code></pre> <p>这里的装饰器类的构造函数中传入func,使其能在__call__方法中被调用。同时这里的装饰器类并没有带有参数,实现不了类似于参数检查的功能。类似于上边的思路,我们这里也可以构建带有参数的装饰器类,还是以参数检查为例:</p> <pre> <code class="language-python"># 构建装饰器类 class ParamCheck(object): def __init__(self, *types, **kwtypes): self.types = types self.kwtypes = kwtypes return def __call__(self, func): @functools.wraps(func) def _inner(*args, **kwargs): result = [isinstance(_param, _type) for _param, _type in zip(args, self.types)] assert all(result), "params_chack: invalid parameters" result = [isinstance(kwargs[_param], self.kwtypes[_param]) for _param in kwargs if _param in self.kwtypes] assert all(result), "params_chack: invalid parameters" return func(*args, **kwargs) return _inner # 使用装饰器 @ParamCheck(int, str, (list, tuple)) def test07(a, b, c): print("in function test06, a=%s, b=%s, c=%s" % (a, b, c)) return 1 # 测试用例 print(test07(1, "str", [1, 2])) # 参数正确 print(test07(1, "str", (1, 2))) # 参数正确 print(test07(1, 2, (1, 2))) # 参数错误</code></pre> <h2><strong>其他一些装饰器实例</strong></h2> <p>函数缓存:一个函数的执行结果可以被缓存在内存中,下次再次调用时,可以先查看缓存中是否存在,如果存在则直接返回缓存中的结果,否则返回函数调用结果。这种装饰器比较适合装饰过程比较复杂或耗时的函数,比如数据库查询等。</p> <pre> <code class="language-python"># 实例: 函数缓存 def funccache(func): cache = {} @functools.wraps(func) def _inner(*args): if args not in cache: cache[args] = func(*args) return cache[args] return _inner # 使用装饰器 @funccache def test08(a, b, c): # 其他复杂或耗时计算 return a + b + c</code></pre> <p>还有很多其他例子,比如函数调用计数、函数计时、函数自动重试等,思路都基本相同,这里就不一一列举了。</p> <h2><strong>Python中自带的装饰器</strong></h2> <p>Python中自带有三个和class相关的装饰器:@staticmethod、@classmethod 和@property。</p> <p>(1)先看@property,可以将其理解为“将类方法转化为类属性的装饰器”。先看实例:</p> <pre> <code class="language-python"># 使用Python自带的装饰器 class People(object): def __init__(self): self._name = None self._age = None return @property def name(self): return self._name @name.setter def name(self, name): self._name = name return @property def age(self): return self._age @age.setter def age(self, age): assert 0 < age < 120 self._age = age return p = People() p.name = "tom" # 设置name p.age = 12 # 设置age print(p.name, p.age) # 输出name和age p.age = 120 # 设置age, 此时认为120为异常数据</code></pre> <p>这里定义一个People类,有两个属性name和age。当我们声明了实例p,使用p操作name和age时,实际上是调用的name、age方法,此时会做参数检查等工作。@property将name方法转化为属性,同时当对该属性进行赋值时,会自动调用@name.setter将下边的name方法。</p> <p>@property有.setter、.getter和.deleter三中装饰器,分别对应赋值、取值和删除三种操作。</p> <p>(2)@staticmethod 将类成员方法声明为类静态方法,类静态方法没有 self 参数,可以通过类名或类实例调用。</p> <p>(3)@classmethod 将类成员方法声明为类方法,类方法所接收的第一个参数不是self,而是cls,即当前类的具体类型。</p> <p>静态方法和类方法都比较简单,一个简单的例子解释静态方法和类方法:</p> <pre> <code class="language-python"># 类静态方法和类方法 class A(object): var = 1 def func(self): print(self.var) return @staticmethod def static_func(): print(A.var) return @classmethod def class_func(cls): print(cls.var) cls().func() return</code></pre> <p> </p> <p> </p> <p>来自:https://zhuanlan.zhihu.com/p/23510985</p> <p> </p>