装饰器和继承:当装饰器应用于父类时会发生啥

Posted

技术标签:

【中文标题】装饰器和继承:当装饰器应用于父类时会发生啥【英文标题】:Decorator and inheritance: what happens when the decorator is applied to parent class装饰器和继承:当装饰器应用于父类时会发生什么 【发布时间】:2021-07-19 22:49:21 【问题描述】:

需要一些帮助来理解装饰器的行为...

这里有一些代码:

import random

class MyDecorator(object):
    """ Logger decorator, adds a 'logger' attribute to the class """
    def __init__(self, *args, **kwargs):
      print(random.randint(1, 100), *args, **kwargs)
      
      self.cls = args[0]

    def __call__(self, *args, **kwargs):
      # Do something, inject some attribute
      setattr(self.cls, 'x', 1234)
      
      # Return an instance of the class
      return self.cls(*args[1:], **kwargs)

@MyDecorator
class A:
  def __init__(self):
    print(f'A self.x')
  
@MyDecorator
class B:
  """ Parent class """
  def __init__(self):
    print(f'B self.x')


class B1(B):
  """ Child class """
  def __init__(self):
    super().__init__()
    
    self.logger.info('Class B1 __init__()')
    

# Here the decorator is applied directly to the class that is going to be instantiated
# Decorator's __init__() receives the class as arg[0], so I can store it and use it when __call__()'ed
a = A()

# Here the decorator is not applied to the class being instantiated, rather to its parent class
# It looks like the decorator's __init__() is being called twice:
# - first time it do recceives the class to which it is applied (in this case, B)
# - second time it receives 3 arguments: a string containing the name of the child class, a tuple containing an instance of the decorator class itself and then some dict containing internal Python controls (I think)
b1 = B1()
    

输出:

82 <class '__main__.A'>
47 <class '__main__.B'>
52 B1 (<__main__.MyDecorator object at 0x7fd4ea9b0860>,) '__module__': '__main__', '__qualname__': 'B1', '__doc__': ' Child class ', '__init__': <function B1.__init__ at 0x7fd4e9785a60>, '__classcell__': <cell at 0x7fd4eaa0f828: empty>
A 1234
Traceback (most recent call last):
  File "main.py", line 39, in <module>
    b1 = B1()
  File "main.py", line 12, in __call__
    setattr(self.cls, 'x', 1234)
AttributeError: 'str' object has no attribute 'x'

所以,我的问题是:

    当我将装饰器应用于父类而不是其子类时会发生什么?看起来装饰器正在为父/子调用,并且在每种情况下都传递了不同的参数集 在这种情况下,我将如何解决“通过装饰器进行类实例化”? (一切都在像 A 这样的情况下正常工作,其中装饰器直接应用于正在实例化的类) 我真的应该返回类的实例吗?如果我需要链接一些装饰器会发生什么?在这种情况下,我的 __call__ 方法应该是什么样子
@MyDecorator1
@MyDecorator2
class A():

感谢您的帮助!

【问题讨论】:

这里的问题是你已经用 MyDecorator 的实例替换了 AB 根据How to Ask,请一次只问一个问题。除了多个问题之外,您的代码还有很多问题,只是被您的代码同时执行多个不相关但不完整的事情所掩盖。 正式地,应用装饰器是在评估classdef 语句后执行的调用,装饰器的结果替换语句的结果。这就是@ 所做的一切。实例化、继承、调用实例化转发、参数集、类作为装饰器等所有恶作剧都使事情变得复杂。您尝试做或测试的一件事是什么? 您的 MyDecorator 实例似乎被用作元类...不知道为什么。 @F.F.Knob 真的,不要使用基于类的装饰器。使用基于函数的,例如def Logger(cls): cls.logger = logger 然后确保return cls。问题是,当您执行@MyDecorator 时,它会实例化MyDecorator 的一个实例,这就是分配给名称AB 的内容,所以当您执行class B1(B): ... 时,然后B 不是B类,它是MyDecorator的一个实例。当您从非类继承时会发生奇怪的事情 【参考方案1】:

所以,当你这样做时:

@MyDecorator
class A:
  def __init__(self):
    print(f'A self.x')

您可以将其视为语法糖

class B:
  def __init__(self):
    print(f'B self.x')

B = MyDecorator(B)

但问题是,现在B MyDecorator 的一个实例,也就是说,现在B 不指代一个类。所以,当你从B 继承时:

class B1(B):
    pass

奇怪的事情发生了。相关的奇怪的事情——最终导致错误的事情——是when deciding on the metaclass to use:

3.3.3.3。确定合适的元类

如果没有给出基数和显式元类,则使用type()

如果给出了一个显式的元类并且它不是type()的实例,那么它直接作为元类使用;

如果type() 的实例作为显式元类给出,或者定义了基类,则使用最派生的元类。

从显式指定的元类(如果有)和所有的元类(即type(cls))中选择派生最多的元类 指定的基类。派生最多的元类是一个 所有这些候选元类的子类型。

即第三个要点发生了,在这种情况下,type(instance_of_my_decorator) 用作元类,即MyDecorator。所以最终,通过不同的途径,B1 现在还引用了MyDecorator 的(另一个)实例,它得到了字符串"B1" 作为构造函数的第一个参数传递,因此在MyDecorator.__init__ 中:

 self.cls = args[0]

正在分配那个字符串,所以当你到达时:

setattr(self.cls, 'x', 1234)

__call__ 中,它失败并出现上述错误。

由于您要做的就是为类分配一个属性,因此最简单的解决方案是不使用基于类的方法,而是使用函数:

def MyDecorator(cls):
    setattr(self.cls, 'x', 1234)
    return cls # important

当然,这似乎有点矫枉过正。您可能应该像往常一样将属性设置为类属性,作为类定义语句的一部分,但如果您坚持使用装饰器,上述方法 起作用。当然,这不会被继承。

【讨论】:

感谢您抽出宝贵时间!真的帮了我很多

以上是关于装饰器和继承:当装饰器应用于父类时会发生啥的主要内容,如果未能解决你的问题,请参考以下文章

装饰器和指令在角度上有啥区别?

装饰器和指令在角度上有啥区别?

Python 装饰器和装饰器模式有啥区别?

Python 装饰器和继承

如何在基类的函数上使用装饰器并在继承基类时让装饰器在该函数上工作

装饰器、包装器和适配器模式之间有啥区别?