5.8. Decorate Method

  • mydecorator is a decorator name

  • method is a method name

  • self is an instance

  • args arbitrary number of positional arguments

  • kwargs arbitrary number of keyword arguments

SetUp:

>>> def mydecorator(method):
...     ...

Syntax:

>>> class MyClass:
...     @mydecorator
...     def mymethod(self, *args, **kwargs):
...         ...

Is equivalent to:

>>> class MyClass:
...     def mymethod(self, *args, **kwargs):
...         ...
>>>
>>>
>>> obj = MyClass()
>>> obj.mymethod = mydecorator(obj.mymethod)

5.8.1. Syntax

  • mydecorator is a decorator name

  • mymethod is a method name

  • self is an instance

  • args arbitrary number of positional arguments

  • kwargs arbitrary number of keyword arguments

>>> def mydecorator(method):
...     def wrapper(self, *args, **kwargs):
...         return method(self, *args, **kwargs)
...     return wrapper
>>>
>>>
>>> class MyClass:
...     @mydecorator
...     def mymethod(self):
...         ...
>>>
>>>
>>> my = MyClass()
>>> my.mymethod()

5.8.2. Example

>>> def run(method):
...     def wrapper(self, *args, **kwargs):
...         return method(self, *args, **kwargs)
...     return wrapper
>>>
>>>
>>> class Astronaut:
...     @run
...     def hello(self, name):
...         return f'My name... {name}'
>>>
>>>
>>> mark = Astronaut()
>>> mark.hello('José Jiménez')
'My name... José Jiménez'

5.8.3. Use Case - 0x01

  • Is Allowed

>>> def if_allowed(method):
...     def wrapper(self, *args, **kwargs):
...         if self._is_allowed:
...             return method(self, *args, **kwargs)
...         else:
...             print('Sorry, Permission Denied')
...     return wrapper
>>>
>>>
>>> class MyClass:
...     def __init__(self):
...         self._is_allowed = True
...
...     @if_allowed
...     def do_something(self):
...         print('Doing...')
...
...     @if_allowed
...     def do_something_else(self):
...         print('Doing something else...')
>>>
>>>
>>> my = MyClass()
>>>
>>> my.do_something()
Doing...
>>> my.do_something_else()
Doing something else...
>>>
>>>
>>> my._is_allowed = False
>>>
>>> my.do_something()
Sorry, Permission Denied
>>> my.do_something_else()
Sorry, Permission Denied

5.8.4. Use Case - 0x02

  • Paragraph

>>> def paragraph(method):
...     def wrapper(self, *args, **kwargs):
...         result = method(self, *args, **kwargs)
...         return f'<p>{result}</p>'
...     return wrapper
>>>
>>>
>>> class HTMLReport:
...     @paragraph
...     def first(self, *args, **kwargs):
...         return 'First'
...
...     @paragraph
...     def second(self, *args, **kwargs):
...         return 'Second'
>>>
>>>
>>> x = HTMLReport()
>>>
>>> x.first()
'<p>First</p>'
>>>
>>> x.second()
'<p>Second</p>'

5.8.5. Assignments

Code 5.74. Solution
"""
* Assignment: Decorator Method Syntax
* Complexity: easy
* Lines of code: 5 lines
* Time: 5 min

English:
    1. Create method decorator `mydecorator`
    2. Decorator should have `wrapper` with `*args` and `**kwargs` parameters
    3. Wrapper should call original method with it's original parameters,
       and return its value
    4. Decorator should return `wrapper` method
    5. Run doctests - all must succeed

Polish:
    1. Stwórz dekorator metod `mydecorator`
    2. Dekorator powinien mieć `wrapper` z parametrami `*args` i `**kwargs`
    3. Wrapper powinien wywoływać oryginalną funkcję z jej oryginalnymi
       parametrami i zwracać jej wartość
    4. Decorator powinien zwracać metodę `wrapper`
    5. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(mydecorator), \
    'Create mydecorator() function'

    >>> assert isfunction(mydecorator(lambda: ...)), \
    'mydecorator() should take method as an argument'

    >>> class MyClass:
    ...     @mydecorator
    ...     def echo(self, text):
    ...         return text

    >>> my = MyClass()
    >>> my.echo('hello')
    'hello'
"""

# type: Callable[[Callable], Callable]
def mydecorator():
    ...

Code 5.75. Solution
"""
* Assignment: Decorator Method Alive
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min

English:
    1. Create `if_alive` method decorator
    2. Decorator will allow running `make_damage` method
       only if `current_health` is greater than 0
    3. Run doctests - all must succeed

Polish:
    1. Stwórz dekorator metody `if_alive`
    2. Dekorator pozwoli na wykonanie metody `make_damage`,
       tylko gdy `current_health` jest większe niż 0
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(if_alive), \
    'Create if_alive() function'

    >>> assert isfunction(if_alive(lambda: ...)), \
    'if_alive() should take method as an argument'

    >>> class Hero:
    ...    def __init__(self, name):
    ...        self.name = name
    ...        self.current_health = 100
    ...
    ...    @if_alive
    ...    def make_damage(self):
    ...        return 10

    >>> hero = Hero('Mark Watney')
    >>> hero.make_damage()
    10
    >>> hero.current_health = -10
    >>> hero.make_damage()
    Traceback (most recent call last):
    RuntimeError: Hero is dead and cannot make damage
"""


# type: Callable[[Callable], Callable]
def if_alive(method):
    def wrapper(hero, *args, **kwargs):
        return method(hero, *args, **kwargs)

    return wrapper