Декораторы по классам - PullRequest
       23

Декораторы по классам

1 голос
/ 07 октября 2019

У меня есть дочерние классы, которые наследуют некоторые базовые функции от родительского класса. Дочерние классы должны иметь универсальный конструктор 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 (с использованием staticmethod), так как он связан с общей функциональностью
  • Каждый дочерний класс (добавлен еще один для иллюстрации) имеет свой собственный prepare_and_connect_constructor classmethod , который вызывает свой собственный конструктор и при необходимости выполняет дополнительную работу

Ответы [ 2 ]

1 голос
/ 07 октября 2019

Декораторы работают на вызовах. Поскольку нет разницы между вызовом функции и инициацией класса, вы можете использовать декоратор непосредственно для класса:

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
1 голос
/ 07 октября 2019

У вас есть небольшое недопонимание магических методов __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__

...