装饰器和继承:当装饰器应用于父类时会发生啥
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
的实例替换了 A
和 B
类
根据How to Ask,请一次只问一个问题。除了多个问题之外,您的代码还有很多问题,只是被您的代码同时执行多个不相关但不完整的事情所掩盖。
正式地,应用装饰器是在评估class
或def
语句后执行的调用,装饰器的结果替换语句的结果。这就是@
所做的一切。实例化、继承、调用实例化转发、参数集、类作为装饰器等所有恶作剧都使事情变得复杂。您尝试做或测试的一件事是什么?
您的 MyDecorator
实例似乎被用作元类...不知道为什么。
@F.F.Knob 真的,不要使用基于类的装饰器。使用基于函数的,例如def Logger(cls): cls.logger = logger
然后确保return cls
。问题是,当您执行@MyDecorator
时,它会实例化MyDecorator
的一个实例,这就是分配给名称A
和B
的内容,所以当您执行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
当然,这似乎有点矫枉过正。您可能应该像往常一样将属性设置为类属性,作为类定义语句的一部分,但如果您坚持使用装饰器,上述方法会 起作用。当然,这不会被继承。
【讨论】:
感谢您抽出宝贵时间!真的帮了我很多以上是关于装饰器和继承:当装饰器应用于父类时会发生啥的主要内容,如果未能解决你的问题,请参考以下文章