【问题标题】:Possible to hijack class definition with decorators?可以用装饰器劫持类定义吗?
【发布时间】:2017-08-02 03:01:19
【问题描述】:

说我有一个

class A:
    def __init__(self, *args):
        pass

我想要一个复制 A 的定义并用新类扩展它的装饰器。

def decorator(cls): # some decorator here
    # make a new class which inherits from A
    # return it while preserving the original A

这可能吗? (PS:这是为了避免维护问题。)

【问题讨论】:

  • 你确定你想要一个新的类,而不是简单地“装饰”现有的类吗?这称为猴子修补,这是一种常见的模式。
  • @HubertGrzeskowiak,是的,我需要两个不同的课程。
  • 是否需要通过装饰器或者创建一个子类就足够了? class ChildClassName (ParentClass1[, ParentClass2, ...]):
  • @DavyM,不,它没有。但是我有很多As,所以为每个创建一个不太可维护。
  • 如果你只想要一个单行来创建一个子类,你可以实例化基元类type。只需执行SA = type('SA', (A,), {}),但如果您解释为什么需要这样做,可能会有更好的解决方案。

标签: python decorator


【解决方案1】:

当您使用装饰器语法调用函数时:

@my_decorator_function
class A:
    pass

装饰器函数的返回值将替换A 的现有定义。因此,如果您希望它创建一个新类并“在保留原始 A 的同时返回它”,您将面临一个棘手的挑战。你的return 将替换A,所以你需要决定它应该是原来的A 还是新的类。你可以把另一个放在别的地方。

例如,这个装饰器会将A 替换为一个子类,并且该子类会将原来的A 类作为一个名为_orig 的类属性提供:

def class_decorator(cls):
    class SubClass(cls):
        _orig = cls
        # add other stuff here?
    return SubClass

如果需要,您可以添加额外的逻辑来将原始类的 __name____doc__ 复制到新类中。你也可以扭转逻辑,在返回未修改的cls之前添加SubClass作为cls的属性。

【讨论】:

  • 这太迷人了!我认为对我来说更好的方法是将新类保留为原始类的属性。非常感谢。
【解决方案2】:

使用@decorator 不是唯一可能的语法。您可以将B = decorator(A) 放在类定义之后。

class A:
    ...

B = decorator(A)

现在您仍然有未修饰的A 的引用,并且您有一个修饰的版本B

【讨论】:

  • 足够好,但如果我可以像装饰器一样在A 上使用单个语句来自动化它会更好......虽然我不得不承认这只是一个语法糖。谢谢。
  • 这在技术上是可行的,但你必须决定哪个名称指向普通类以及哪个名称指向装饰类(并且你必须在问题中描述它应该如何工作)。因为A 不能同时指向旧类和新类。
【解决方案3】:

其他答案做得很好,但要清楚为什么你不想这样做:

def dec(cls):
    new_cls = type(cls.__name__, (cls,), {})
    return new_cls

@dec
class A():
    pass

现在检查方法解析顺序类A

>>> A.__mro__
(<class '__main__.A'>, <class '__main__.A'>, <class 'object'>)
>>> classes = A.__mro__
>>> classes[0].__name__
'A'
>>> classes[1].__name__
'A'

两班As!它们是一样的吗?

>>> classes[0] is classes[1]
False

不;不同的。当前变量A当然是指向最低的一个:

>>> A is classes[0]
True

但是现在您已经失去了对父级的名称访问权限。这通常不是最佳的。

简而言之:几个月后,当您忘记自己所做的一切时,您正在为自己制造大量的困惑和模棱两可。做点别的。

如果你真的想要,这里有一个衍生出新子类的想法:

def add_babymaker(cls):
    '''Adds a method for making new child classes.'''
    def babymaker(name=None):
        '''Creates a new child class based on the parent class.'''
        name = name if name is not None else cls.__name__
        new_cls = type(name, (cls,), {})
        return new_cls
    cls.babymaker = babymaker
    return cls

@add_babymaker
class A():
    pass

B = A.babymaker('B')
C = A.babymaker('C')
ANew = A.babymaker()

【讨论】:

    【解决方案4】:

    我想我已经解决了。这不是一个好主意。

    def make_variant(cls):
        suffix='VARIANT'
        new = type(cls.__name__+suffix, (cls, ), {})
        # new.__repr__ = lambda self: 'HELLO' # Just do whatever needed here
        assert cls.__name__ + suffix not in globals()
        globals()[cls.__name__+suffix] = new # Think twice about this line
        return cls
    
    @make_variant
    class A:
        def __init__(self):
            pass
    
    print(AVARIANT(), A())
    

    【讨论】:

    • 将新名称转储到全局变量中的装饰器不是一个好主意。这不是人们期望装饰器做的事情,并且可能会使 IDE 和 linter 变得混乱(认为您有未解析的名称)。我不敢相信这种方法会帮助“避免维护问题”,更像是创建它们......
    • @wim,同意,它解决了问题并不意味着它不会做更多...... Blckknght 的解决方案很棒。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-20
    • 2011-05-18
    • 2018-12-03
    • 1970-01-01
    • 2011-04-08
    • 1970-01-01
    • 2023-03-16
    相关资源
    最近更新 更多