4.4. Function Decorator with Methods

4.4.1. Rationale

  • mydecorator is a decorator name

  • method is a method name

  • instance is an instance

  • args arbitrary number of positional arguments

  • kwargs arbitrary number of keyword arguments

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


obj = MyClass()
obj.mymethod()
Is equivalent to:
class MyClass:
    def mymethod(self, *args, **kwargs):
        ...


obj = MyClass()
obj.mymethod = mydecorator(obj.mymethod)

4.4.2. Syntax

  • mydecorator is a decorator name

  • mymethod is a method name

  • instance is an instance

  • args arbitrary number of positional arguments

  • kwargs arbitrary number of keyword arguments

Listing 4.66. Definition
def mydecorator(method):
    def wrapper(instance, *args, **kwargs):
        return method(instance, *args, **kwargs)
    return wrapper
Listing 4.67. Decoration
class MyClass:

    @mydecorator
    def mymethod(self):
        ...
Listing 4.68. Usage
my = MyClass()
my.mymethod()

4.4.3. Example

def run(method):
    def wrapper(instance, *args, **kwargs):
        return method(instance, *args, **kwargs)
    return wrapper


class Astronaut:
    @run
    def hello(self, name):
        return f'My name... {name}'


astro = Astronaut()
astro.hello('José Jiménez')
# 'My name... José Jiménez'

4.4.4. Use Cases

def if_allowed(method):
    def wrapper(instance, *args, **kwargs):
        if instance._is_allowed:
            return method(instance, *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, you cannot do anything
my.do_something_else()      # Sorry, you cannot do anything
def paragraph(method):
    def wrapper(instance, *args, **kwargs):
        result = method(instance, *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>'

4.4.5. Assignments

4.4.5.1. Decorator Methods Alive

  • Assignment name: Decorator Methods Alive

  • Last update: 2020-10-01

  • Complexity level: easy

  • Lines of code to write: 5 lines

  • Estimated time of completion: 13 min

  • Solution: solution/decorator_method_alive.py

English
  1. Use data from "Input" section (see below)

  2. Create if_alive method decorator

  3. Decorator will allow running make_damage method only if current_health is greater than 0

  4. Compare result with "Output" section (see below)

Polish
  1. Użyj kodu z sekcji "Input" (patrz poniżej)

  2. Stwórz dekorator metod if_alive

  3. Dekorator pozwoli na wykonanie metody make_damage, tylko gdy current_health jest większe niż 0

  4. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Input
class Hero:
    def __init__(self, name):
        self.name = name
        self.current_health = 100

    @if_alive
    def make_damage(self):
        return 10
Output
>>> hero = Hero('Jan Twardowski')
>>> hero.make_damage()
10

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