简体   繁体   中英

Python - class decorator - runtime inheritance

I am trying to create a decorator that will inject some functionality to wrapped class __init__ method. This is what works.

class Decorator:
    def __init__(self, arg):
        print(arg)
        self.arg = arg

    def __call__(self, cls):
        print(cls)

        class Wrapped(cls):
            def __init__(self, first_arg, second_arg, **kwargs):
                cls.__init__(self, first_arg, second_arg, **kwargs)
                print('in wrapped init', self.variable)

        return Wrapped


@Decorator('random_string')
class TestClass:
    def __init__(self, first_arg, second_arg, **kwargs):
        self.variable = 10
        print('TestClass init')


test = TestClass(first_arg='one', second_arg='two')

and produces

random_string
<class '__main__.TestClass'>
TestClass init
in wrapped init 10

for some mysterious reasons code is no longer working after removing decorator param (random string in this case)

@Decorator
class TestClass:
    def __init__(self, first_arg, second_arg, **kwargs):
        self.variable = 10
        print('TestClass init')

Output:

Traceback (most recent call last):
  File "/home/python_examples/test_decorators.py", line 24, in <module>
    test = TestClass(first_arg='one', second_arg='two')
<class '__main__.TestClass'>
TypeError: __call__() got an unexpected keyword argument 'first_arg'

Two questions:

  • is this a well-known and valid approach to decorate classes?
  • why is the never used 'random_string' param crucial?

It's because your decorator class takes arg as a constructor argument. So when you remove your decorator param, make sure that you have removed that parameter from the __init__ method too. Changing this

class Decorator:
    def __init__(self, arg):
        print(arg)
        self.arg = arg

    def __call__(self, cls):
        print(cls)

        class Wrapped(cls):
            def __init__(self, first_arg, second_arg, **kwargs):
                cls.__init__(self, first_arg, second_arg, **kwargs)
                print('in wrapped init', self.variable)

        return Wrapped

to this

class Decorator:
    def __init__(self):
        pass

    def __call__(self, cls):
        print(cls)

        class Wrapped(cls):
            def __init__(self, first_arg, second_arg, **kwargs):
                cls.__init__(self, first_arg, second_arg, **kwargs)
                print('in wrapped init', self.variable)

        return Wrapped

will solve your problem.

Actually, this is how it works:

# Assume you have a decorator class and a class named A which is needed to decorate.

@Decorator
class A:
    ...

# is the same as 

class A:
    ...

A = Decorator()(A)

That's why you need to define __cal__ .

And here you can see that if your Decorator accepts some parameters to initialize, you need to use something like A = Decorator(xxx)(A) . And the equivalent in decorator syntax is:

@Decorator(xxx)
class A:
    ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM