Super doesn't work with class decorators?

Allows you to define a simple class decorator function that creates a subclass and adds 'Dec' only to the original class name:

def decorate_class(klass): new_class = type(klass.__name__ + 'Dec', (klass,), {}) return new_class 

Now apply it to a simple subclass definition:

 class Base(object): def __init__(self): print 'Base init' @decorate_class class MyClass(Base): def __init__(self): print 'MyClass init' super(MyClass, self).__init__() 

Now, if you try to create an instance of MyClass , it will end in an endless loop:

 c = MyClass() # ... # File "test.py", line 40, in __init__ # super(MyClass, self).__init__() # RuntimeError: maximum recursion depth exceeded while calling a Python object 

It seems that super cannot handle this case and does not skip the current class from the inheritance chain.

The question is, how to use the class decorator in classes using super ?

Bonus question, how to get the final class from the proxy created by super ? I.e. get object class from the expression super(Base, self).__init__ , as defined by the definition of the parent class, called __init__ .

+5
source share
3 answers

If you just want to change the class .__name__ , create a decorator that will do this.

 from __future__ import print_function def decorate_class(klass): klass.__name__ += 'Dec' return klass class Base(object): def __init__(self): print('Base init') @decorate_class class MyClass(Base): def __init__(self): print('MyClass init') super(MyClass, self).__init__() c = MyClass() cls = c.__class__ print(cls, cls.__name__) 

Python 2 exit

 MyClass init Base init <class '__main__.MyClassDec'> MyClassDec 

Python 3 exit

 MyClass init Base init <class '__main__.MyClass'> MyClassDec 

Note the difference in cls representation. (I'm not sure why you want to change the class name, although it sounds like a recipe for confusion, but I think this is normal for this simple example).

As others have said, @decorator not intended to subclass. You can do this in Python 3 using the super argument (i.e., super().__init__() ). And you can make it work in both Python 3 and Python 2 by explicitly providing the parent class, rather than using super .

 from __future__ import print_function def decorate_class(klass): name = klass.__name__ return type(name + 'Dec', (klass,), {}) class Base(object): def __init__(self): print('Base init') @decorate_class class MyClass(Base): def __init__(self): print('MyClass init') Base.__init__(self) c = MyClass() cls = c.__class__ print(cls, cls.__name__) 

Python 2 and 3 output

 MyClass init Base init <class '__main__.MyClassDec'> MyClassDec 

Finally, if we just call decorate_class using the usual function syntax, and not like @decorator , we can use super .

 from __future__ import print_function def decorate_class(klass): name = klass.__name__ return type(name + 'Dec', (klass,), {}) class Base(object): def __init__(self): print('Base init') class MyClass(Base): def __init__(self): print('MyClass init') super(MyClass, self).__init__() MyClassDec = decorate_class(MyClass) c = MyClassDec() cls = c.__class__ print(cls, cls.__name__) 

The conclusion is the same as in the latest version.

+2
source

Since your decorator returns a completely new class with a different name, an object does not even exist for this MyClass class. This is not the class for which decorators are intended. They are intended to add additional functionality to an existing class, and not to replace it with another class.

However, if you are using Python3, the solution is simple -

 @decorate_class class MyClass(Base): def __init__(self): print 'MyClass init' super().__init__() 

Otherwise, I doubt that there is a direct solution, you just need to change your implementation. When you rename a class, you need to rewrite the new name on top of __init__ as well.

+2
source

The problem is that your decorator subclasses the source. This means that super(Myclass) now points to ... the original class!

I can’t even explain how form 0 arg from super succeeds in doing the job in Python 3, I couldn’t find anything explicit in the reference manual. I assume that it should use the class in which it is used during the declaration. But I can not imagine a way to get this result in Python2.

If you want to use super in a decorated class in Python 2, you should not create a derived class, but directly modify the original class in place.

For example, here is a decorator that prints a line before and after calling any method:

 def decorate_class(klass): for name, method in klass.__dict__.iteritems(): # iterate the class attributes if isinstance(method, types.FunctionType): # identify the methods def meth(*args, **kwargs): # define a wrapper print "Before", name method(*args, **kwargs) print "After", name setattr(klass, name, meth) # tell the class to use the wrapper return klass 

In your example, this gives as expected:

 >>> c = MyClass() Before __init__ MyClass init Base init After __init__ 
0
source

Source: https://habr.com/ru/post/1267230/


All Articles