Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting function call after __init__ with decorator

I'm trying to find the best way to create a class decorator that does the following:

  1. Injects a few functions into the decorated class
  2. Forces a call to one of these functions AFTER the decorated class' __init__ is called

Currently, I'm just saving off a reference to the 'original' __init__ method and replacing it with my __init__ that calls the original and my additional function. It looks similar to this:

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

Is there a better way to 'augment' the original __init__ or inject my call somewhere else? All I really need is for my self._debugSignals() to be called sometime after the object is created. I also want it happen automatically, which is why I thought after __init__ was a good place.

Extra misc. decorator notes

It might be worth mentioning some background on this decorator. You can find the full code here. The point of the decorator is to automatically attach to any PyQt signals and print when they are emitted. The decorator works fine when I decorate my own subclasses of QtCore.QObject, however I've been recently trying to automatically decorate all QObject children.

I'd like to have a 'debug' mode in the application where I can automatically print ALL signals just to make sure things are doing what I expect. I'm sure this will result in TONS of debug, but I'd still like to see what's happening.

The problem is my current version of the decorator is causing a segfault when replacing QtCore.QObject.__init__. I've tried to debug this, but the code is all SIP generated, which I don't have much experience with.

So, I was wondering if there was a safer, more pythonic way to inject a function call AFTER the __init__ and hopefully avoid the segfault.

like image 463
durden2.0 Avatar asked Apr 15 '13 14:04

durden2.0


People also ask

Can you call methods in __ init __?

Use self. f() to call a class method inside of __init__ Within the method definition of __init__(self) , call self. f() where f is the name of the class method.

Is __ init __ automatically called?

Note: The __init__() function is called automatically every time the class is being used to create a new object.

Can we use decorator inside a function in Python?

Nesting means placing or storing inside the other. Therefore, Nested Decorators means applying more than one decorator inside a function. Python allows us to implement more than one decorator to a function. It makes decorators useful for reusable building blocks as it accumulates the several effects together.

What does __ call __ do in Python?

The __call__ method enables Python programmers to write classes where the instances behave like functions and can be called like a function. When the instance is called as a function; if this method is defined, x(arg1, arg2, ...) is a shorthand for x. __call__(arg1, arg2, ...) .


2 Answers

Based on this post and this answer, an alternative way to do this is through a custom metaclass. This would work as follows (tested in 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!!

The basic idea is that:

  1. when you call MyNewClass() it searches for the metaclass, finds that you have defined NewInitCaller

  2. The metaclass __call__ function is called.

  3. This function creates the MyNewClass instance using type,

  4. The instance runs its own __init__ (printing "Init class").

  5. The meta class then calls the new_init function of the instance.

like image 67
will-hart Avatar answered Oct 18 '22 13:10

will-hart


Here is the solution for Python 3.x, based on this post's accepted answer. Also see PEP 3115 for reference, I think the rationale is an interesting read.

Changes in the example above are shown with comments; the only real change is the way the metaclass is defined, all other are trivial 2to3 modifications.

# 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!!
like image 26
jake77 Avatar answered Oct 18 '22 13:10

jake77