Добавить декоратор в метод из унаследованного класса? - PullRequest
0 голосов
/ 16 мая 2018

Я хочу наследовать от класса, просто чтобы добавить декораторы к его методам.

Есть ли ярлык для этого без переопределения каждого метода базового класса?

Ответы [ 2 ]

0 голосов
/ 16 мая 2018

Вы можете использовать декоратор класса для инкапсуляции всей работы

Пример кода:

def deco(func):
    """Function decorator"""
    def inner(*args, **kwargs):
        print("decorated version")
        return func(*args, **kwargs)
    return inner

def decoclass(decorator):
    """Class decorator: decorates public methods with decorator"""
    def outer(cls):
        class inner(cls):
            pass
        for name in dir(cls):
            if not name.startswith("_"):     # ignore hidden and private members
                # print("decorating", name)  # uncomment for tests
                attr = getattr(inner, name)
                setattr(inner, name, decorator(attr))
        return inner
    return outer

class Foo:
    """Sample class""
    def foo(self):
        return "foo in Foo"

Затем вы можете использовать его:

>>> @decoclass(deco)
class Foo2(Foo):
    pass

>>> f = Foo2()
>>> f.foo()
decorated version
'foo in Foo'
0 голосов
/ 16 мая 2018

Конечно, вы можете сделать это динамически. Предположим, у вас есть класс:

>>> class Foo:
...    def bar(self): print('bar')
...    def baz(self): print('baz')
...

И декоратор:

>>> def deco(f):
...    def wrapper(self):
...       print('decorated')
...       return f(self)
...    return wrapper
...

Тогда просто наследуй:

>>> class Foo2(Foo):
...     pass
...

Затем переберите исходный класс и примените декоратор к новому дочернему классу:

>>> for name, attr in vars(Foo).items():
...     if callable(attr):
...         setattr(Foo2, name, deco(attr))
...

Итак ...

>>> x = Foo2()
>>> x.bar()
decorated
bar
>>> x.baz()
decorated
baz

Теперь использование if callable(attr) может быть недостаточно ограничительным. Возможно, вы захотите игнорировать «более сложные» методы, поэтому вместо этого:

for name, attr in vars(Foo):
    if callable(attr) and not name.startswith('__'):
        setattr(Foo2, name, attr)

может быть более подходящим. Зависит от вашего варианта использования.

И просто для удовольствия, здесь мы также можем использовать конструктор type:

In [17]: class Foo:
    ...:     def bar(self): print('bar')
    ...:     def baz(self): print('baz')
    ...:

In [18]: def deco(f):
    ...:     def wrapper(self):
    ...:         print('decorated')
    ...:         return f(self)
    ...:     return wrapper
    ...:

In [19]: Foo3 = type(
    ...:     'Foo3',
    ...:     (Foo,),
    ...:     {name:deco(attr) for name, attr in vars(Foo).items() if callable(attr)}
    ...: )

In [20]: y = Foo3()

In [21]: y.bar()
decorated
bar

In [22]: y.baz()
decorated
baz
...