Python进阶: 通过实例详解装饰器

Latrice2344 7年前
   <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>