【问题标题】:Decorators on classmethods类方法上的装饰器
【发布时间】:2020-02-05 02:25:24
【问题描述】:

我有从父类继承一些基本功能的子类。 子类应该有一个通用构造函数prepare_and_connect_constructor(),它围绕父类的对象创建做了一些魔术。 为简单起见,魔术是由一个简单的基于函数的装饰器完成的(最终,它应该是父类的一部分)。

def decorate_with_some_magic(func):
    def prepare_and_connect(*args, **kwargs):
        print("prepare something")
        print("create an object")
        obj = func(*args, **kwargs)
        print("connect obj to something")
        return obj

    return prepare_and_connect


class Parent:

    def __init__(self, a):
        self.a = a

    def __repr__(self):
        return f"{self.a}"


class Child(Parent):

    @classmethod
    @decorate_with_some_magic
    def prepare_and_connect_constructor(cls, a, b):
        """ use the generic connection decorator right on object creation """
        obj = super().__init__(a)
        # put some more specific attributes (over the parents class)
        obj.b = b
        return obj

    def __init__(self, a, b):
        """ init without connecting """
        super().__init__(a)
        self.b = b

    def __repr__(self):
        return f"{self.a}, {self.b}"


if __name__ == '__main__':
    print(Child.prepare_and_connect_constructor("special child", "needs some help"))

使用这段代码我终于得到了

obj = super().__init__(a)
  TypeError: __init__() missing 1 required positional argument: 'a'

运行prepare_and_connect_constructor()时。

实际上我希望super.__init__(a) 调用应该与Child.__init__ 中的相同。 我猜原因与classmethod有关,但我想不通。

这个电话有什么问题?

更新:总的来说,问题在于__init__ 没有返回对象。

由于答案中的提示和想法,我修改了我的代码以实现我所需要的:

class Parent:

    def __init__(self, a):
        self.a = a

    @staticmethod
    def decorate_with_some_magic(func):
        def prepare_and_connect(*args, **kwargs):
            print("prepare something")
            print("create an object")
            obj = func(*args, **kwargs)
            print("connect obj to something")
            return obj

        return prepare_and_connect

    def __repr__(self):
        return f"{self.a}"


class ChildWithOneName(Parent):

    @classmethod
    @Parent.decorate_with_some_magic
    def prepare_and_connect_constructor(cls, a, b):
        """ use the generic connection decorator right on object creation """
        obj = super().__new__(cls)
        obj.__init__(a, b)
        print("Does the same as in it's __init__ method")
        return obj

    def __init__(self, a, b):
        """ init without connecting """
        super().__init__(a)
        self.b = b

    def __repr__(self):
        return f"{self.a}, {self.b}"


class GodChild(Parent):

    @classmethod
    @Parent.decorate_with_some_magic
    def prepare_and_connect_constructor(cls, a, names):
        """ use the generic connection decorator right on object creation """
        obj = super().__new__(cls)
        obj.__init__(a, names)
        # perform some more specific operations
        obj.register_all_names(names)
        print("And does some more stuff than in it's __init__ method")
        return obj

    def __init__(self, a, already_verified_names):
        """ init without connecting """
        super().__init__(a)
        self.verified_names = already_verified_names

    def register_all_names(self, names=[]):
        self.verified_names = []

        def verify(text):
            return True

        for name in names:
            if verify(name):
                self.verified_names.append(name)


    def __repr__(self):
        return f"{self.a}, {self.verified_names}"


if __name__ == '__main__':
    print(ChildWithOneName.prepare_and_connect_constructor("special child", "needs some help"), end='\n\n')
    print(GodChild.prepare_and_connect_constructor("unknown child", "needs some verification"), end='\n\n')
    print(ChildWithOneName("my child", "is clean and doesn't need extra magic"))
  • decorate_with_some_magic 现在是 Parent 类的一部分(使用静态方法),因为它是一个相关的通用功能
  • 每个子类(为了说明而添加了一个)都有自己的prepare_and_connect_constructor classmethod,它调用自己的构造函数并选择性地执行一些额外的工作

【问题讨论】:

  • __init__ 不返回任何内容,并且由于您从类方法调用supersuper().__init__ 需要一个显式实例作为其第一个参数。
  • 不错,没想到这个

标签: python decorator class-method


【解决方案1】:

你对__init____new__的魔术方法有一点误解。 __new__ 创建一个新对象,例如返回该类的一个实例。 __init__ 只是在原地修改对象。因此,解决您的问题的一个简单方法是:

@classmethod
@decorate_with_some_magic
def prepare_and_connect_constructor(cls, a, b):
    """ use the generic connection decorator right on object creation """
    obj = super().__new__(cls)
    obj.__init__(a)
    # put some more specific attributes (over the parents class)
    obj.b = b
    return obj

但是,我认为您不应该这样使用它。相反,您可能应该覆盖__new__

【讨论】:

  • 应该对__new__进行什么更改才能正确执行?
  • @maggie 假设您总是想连接。是这样吗?
  • 不,不是,这就是我问的原因。它应该是一种替代方案(编写更少的代码)。但情况并非总是如此。
【解决方案2】:

装饰器处理可调用对象。由于调用函数和初始化类没有区别,你可以直接在类上使用你的装饰器:

def decorate_with_some_magic(func):
    def prepare_and_connect(*args, **kwargs):
        print("prepare something")
        print("create an object")
        obj = func(*args, **kwargs)
        print("connect obj to something")
        return obj

    return prepare_and_connect


class Parent:

    @classmethod
    def prepare_and_connect_constructor(cls, a, b):
        return decorate_with_some_magic(cls)(a, b)

    def __init__(self, a):
        self.a = a

    def __repr__(self):
        return f"{self.a}"

class Child(Parent):

    def __init__(self, a, b):
        """ init without connecting """
        super().__init__(a)
        self.b = b


    def __repr__(self):
        return f"{self.a}, {self.b}"


if __name__ == '__main__':
    normal_child = Child("normal child", "no help needed")
    print(normal_child)
    special_child = Child.prepare_and_connect_constructor("special child", "needs some help")
    print(special_child)

输出:

normal child, no help needed
prepare something
create an object
connect obj to something
special child, needs some help

【讨论】:

  • 但我想要一个“普通”构造函数和一个替代构造函数。那是不可能的。
  • 在类“外部”使用装饰器。请参阅我修改后的答案。
  • 好的,这很有效,看起来不错,但是对于多个子类,这些类的工作方式不同。它不能保证在普通构造函数中会发生同样的事情......也许它不是我想的最好的设计,但不能用类方法来做吗?
  • 现在使用类方法。
猜你喜欢
  • 2014-01-14
  • 2020-01-11
  • 1970-01-01
  • 1970-01-01
  • 2022-01-06
  • 1970-01-01
  • 2012-09-11
  • 1970-01-01
  • 2018-07-14
相关资源
最近更新 更多