6.13. 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)

6.13.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()

6.13.2. Example

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

6.13.3. Use Case - 1

  • 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

6.13.4. Use Case - 2

  • 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>'

6.13.5. Use Case - 3

>>> def login_required(obj):
...     def wrapper(instance, *args, **kwargs):
...         if not instance.is_authenticated:
...             raise PermissionError
...         return obj(instance, *args, **kwargs)
...     return wrapper
>>>
>>>
>>> class User:
...     def __init__(self, username, password):
...         self.username = username
...         self.password = password
...         self.is_authenticated = False
...
...     def login(self):
...         self.is_authenticated = True
...         print('User login')
...
...     def logout(self):
...         self.is_authenticated = False
...         print('User logout')
...
...     @login_required
...     def change_password(self, new_password):
...         self.password = new_password
>>> mark = User('mwatney', 'Ares3')
>>> mark.password
'Ares3'
>>> mark.change_password('Nasa69')
Traceback (most recent call last):
PermissionError
>>> mark.login()
User login
>>>
>>> mark.change_password('Nasa69')
>>> mark.password
'Nasa69'
>>> mark.logout()
User logout
>>> mark.change_password('Ares3')
Traceback (most recent call last):
PermissionError

6.13.6. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Decorator Method Syntax
# - Difficulty: easy
# - Lines: 5
# - Minutes: 3

# %% 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
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> 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():
    ...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Decorator Method Alive
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3

# %% 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
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> 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