Внедрение вызова функции после __init__ с помощью декоратора

Я пытаюсь найти лучший способ создать декоратор класса, который делает следующее:

  1. Внедряет несколько функций в украшенный класс
  2. Принудительно вызывает одну из этих функций ПОСЛЕ того, как декорированный класс __init__ вызывается

В настоящее время я просто сохраняю ссылку на «оригинальный» метод __init__ и заменяю ее своим __init__, который вызывает оригинал и мою дополнительную функцию. Это выглядит примерно так:

orig_init = cls.__init__

def new_init(self, *args, **kwargs):
    """
    'Extend' wrapped class' __init__ so we can attach to all signals
    automatically
    """

    orig_init(self, *args, **kwargs)
    self._debugSignals()

cls.__init__ = new_init

Есть ли лучший способ «дополнить» исходный __init__ или внедрить мой вызов в другое место? Все, что мне действительно нужно, это чтобы мой self._debugSignals() вызывался через некоторое время после создания объекта. Я также хочу, чтобы это происходило автоматически, поэтому я подумал, что после __init__ будет хорошее место.

Дополнительно разное. заметки декоратора

Возможно, стоит упомянуть некоторую предысторию этого декоратора. Полный код можно найти здесь. Смысл декоратора в том, чтобы автоматически присоединяться к любым сигналам PyQt и печатать, когда они испускаются. Декоратор отлично работает, когда я украшаю свои собственные подклассы QtCore.QObject, однако недавно я пытался автоматически декорировать все дочерние объекты QObject.

Я хотел бы иметь режим «отладки» в приложении, где я могу автоматически печатать ВСЕ сигналы, просто чтобы убедиться, что все работает так, как я ожидаю. Я уверен, что это приведет к ТОННАМ отладки, но я все равно хотел бы посмотреть, что происходит.

Проблема в том, что моя текущая версия декоратора вызывает segfault при замене QtCore.QObject.__init__. Я пытался отладить это, но весь код сгенерирован SIP, с которым у меня мало опыта.

Итак, мне было интересно, есть ли более безопасный, более питонический способ внедрить вызов функции ПОСЛЕ __init__ и, надеюсь, избежать segfault.


person durden2.0    schedule 15.04.2013    source источник
comment
Не решение, а некоторая информация: QtCore.QObject.__init__ реализовано внутри библиотеки Qt, т.е. в C++, поэтому вы не можете просто заменить __init__.   -  person rainer    schedule 15.04.2013
comment
@rainer Это имеет смысл. Я попытался просмотреть исходный код QtCore.QObject.__init__, но запутался во всей магии sip/C++, которая создает оболочки и т. д. Это объясняет, почему мой декоратор работает с объектами на основе Python (или, по крайней мере, с теми, которые не так тесно связаны с слой sip/С++). Однако я подумал, может быть, есть более чистый способ добиться такого поведения без перехвата __init__, потому что мне тоже не очень нравится такое поведение для объектов на основе python. Однако «это просто работает» для объектов python.   -  person durden2.0    schedule 15.04.2013


Ответы (2)


На основе этого сообщения и этот ответ, альтернативный способ сделать это через пользовательский метакласс. Это будет работать следующим образом (проверено в Python 2.7):

# define a new metaclass which overrides the "__call__" function
class NewInitCaller(type):
    def __call__(cls, *args, **kwargs):
        """Called when you call MyNewClass() """
        obj = type.__call__(cls, *args, **kwargs)
        obj.new_init()
        return obj


# then create a new class with the __metaclass__ set as our custom metaclass
class MyNewClass(object):
    __metaclass__ = NewInitCaller
    def __init__(self):
        print "Init class"
    def new_init(self):
        print "New init!!"

# when you create an instance
a = MyNewClass()
>>> Init class
>>> New init!!

Основная идея заключается в том, что:

  1. когда вы вызываете MyNewClass(), он ищет метакласс, находит, что вы определили NewInitCaller

  2. Вызывается функция метакласса __call__.

  3. Эта функция создает экземпляр MyNewClass, используя type,

  4. Экземпляр запускает свой собственный __init__ (вывод "Init class").

  5. Затем метакласс вызывает функцию new_init экземпляра.

person will-hart    schedule 05.07.2013
comment
Это не сработает для моего конкретного сценария с PyQt и QtCore.QObject. Это связано с тем, что объект, который я пытаюсь исправить, реализован на C, а не на чистом Python. Однако это решение будет хорошо работать для чистого объекта Python. - person durden2.0; 11.07.2013
comment
Ах... это боль. Кстати, ваш декоратор не возвращает cls в конце. Я предполагаю, что вы только что опубликовали выдержку? - person will-hart; 11.07.2013
comment
Да, только что выложил фрагмент. Настоящий декоратор должен вернуть cls. - person durden2.0; 12.07.2013

Вот решение для Python 3.x, основанное на принятом ответе на этот пост. Также см. PEP 3115 для справки, я думаю, что это интересное объяснение .

Изменения в приведенном выше примере показаны с комментариями; единственное реальное изменение - это способ определения метакласса, все остальные - тривиальные модификации 2-3.

# define a new metaclass which overrides the "__call__" function
class NewInitCaller(type):
    def __call__(cls, *args, **kwargs):
        """Called when you call MyNewClass() """
        obj = type.__call__(cls, *args, **kwargs)
        obj.new_init()
        return obj

# then create a new class with the metaclass passed as an argument
class MyNewClass(object, metaclass=NewInitCaller):  # added argument
    # __metaclass__ = NewInitCaller  this line is removed; would not have effect
    def __init__(self):
        print("Init class")  # function, not command
    def new_init(self):
        print("New init!!")  # function, not command

# when you create an instance
a = MyNewClass()
>>> Init class
>>> New init!!
person jake77    schedule 19.10.2017