Украшение методов класса путем переопределения __new__ не работает? - PullRequest
0 голосов
/ 04 февраля 2019

Я хочу украсить все методы моего класса.Я написал образец небольшого декоратора для иллюстрации.

Декоратор:

def debug(func):
    msg = func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        print(msg)
        return func(*args, **kwargs)
    return wrapper 

def debugmethods(cls):
    for key, val in vars(cls).items():
        if callable(val):
            setattr(cls, key, debug(val))
    return cls

Теперь я хочу украсить все методы моего класса.Один простой способ - использовать аннотацию @debugmethods поверх моего класса, но я пытаюсь понять два других различных подхода для этого.

a) Переопределение __new__

class Spam:
    def __new__(cls, *args, **kwargs):
        clsobj = super().__new__(cls)
        clsobj = debugmethods(clsobj)
        return clsobj

    def __init__(self):
        pass

    def foo(self):
        pass

    def bar(self):
        pass

spam = Spam()
spam.foo()

b) Написание метакласса

class debugmeta(type):
    def __new__(cls, clsname, bases, clsdict):
        clsobj = super().__new__(cls, clsname, bases, clsdict)
        clsobj = debugmethods(clsobj)
        return clsobj

class Spam(metaclass = debugmeta):     
    def foo(self):
        pass

    def bar(self):
        pass

spam = Spam()
spam.foo()

Я не уверен

  1. Почему «а) переопределение __new__» не работает?
  2. Почему подпись метода __new__ отличается в метаклассе?

Может кто-нибудь помочь мне понять, что мне здесь не хватает.

1 Ответ

0 голосов
/ 04 февраля 2019

Вы, кажется, запутались между __new__ и метаклассами.__new__ вызывается для создания нового объекта (экземпляр из класса, класс из метакласса), это не крюк «класс создан».

Обычный шаблон:

  • Foo(...) переведено на type(Foo).__call__(Foo, ...), см. поиск специальных методов , почему это так.type() класса - это его метакласс.
  • Стандартная реализация type.__call__, используемая, когда Foo - это пользовательский класс Python, вызовет __new__ для создания нового экземпляра, а затем вызовет__init__ метод в этом экземпляре, если результат действительно является экземпляром класса Foo:

    def __call__(cls, *args, **kwargs):  # Foo(...) -> cls=Foo
        instance = cls.__new__(cls, *args, **kwargs)  # so Foo.__new__(Foo, ...)
        if isinstance(instance, cls):
            instance.__init__(*args, **kwargs)        # Foo.__init__(instance, ...)
        return instance
    

    Так что Foo.__new__ не вызывается при создании самого класса Foo, только когда экземпляров из Foo созданы.

Обычно вам не нужно использовать __new__ в классах, потому что __init__ достаточно для инициализации атрибутов экземпляров,Но для неизменяемых типов, таких как int или tuple, вы можете использовать __new__ только для подготовки нового состояния экземпляра, так как вы не можете изменять атрибуты неизменяемого объекта после его создания.,__new__ также полезен, когда вы хотите изменить тип экземпляров ClassObj() (например, вместо создания синглетонов или создания специализированных подклассов).

То же __call__ -> __new__ и, возможно, __init__ процесс применяется к метаклассам.Оператор class Foo: ... реализуется путем вызова метакласса для создания объекта класса, передавая 3 аргумента: имя класса, базы классов и тело класса, как обычно словарь.С class Spam(metaclass = debugmeta): ... это означает, что вызывается debugmeta('Spam', (), {...}), что означает, что вызывается debugmeta.__new__(debugmeta, 'Spam', (), {...}).

Ваша первая попытка a , настройка Spam.__new__ не работает, потому что вы нет создания объекта класса там.Вместо этого super().__new__(cls) создает пустой Spam() экземпляр без атрибутов, поэтому vars() возвращает пустой словарь, а debugmethods() в итоге ничего не делает.

Если вы хотите подключитьв создание класса, то вы хотите метакласс.

...