5.14. Decorator Stdlib Functools

5.14.1. Wraps

  • from functools import wraps

  • @wraps(func)

Without Wraps:

>>> def mydecorator(func):
...     def wrapper(*args, **kwargs):
...         """wrapper docstring"""
...         return func(*args, **kwargs)
...     return wrapper
>>>
>>>
>>> @mydecorator
... def myfunction(x):
...     """myfunction docstring"""
...     print(x)
>>>
>>>
>>> print(myfunction.__name__)
wrapper
>>>
>>> print(myfunction.__doc__)
wrapper docstring

With Wraps:

>>> from functools import wraps
>>>
>>>
>>> def mydecorator(func):
...     @wraps(func)
...     def wrapper(*args, **kwargs):
...         """wrapper docstring"""
...         return func(*args, **kwargs)
...     return wrapper
>>>
>>>
>>> @mydecorator
... def myfunction(x):
...     """myfunction docstring"""
...     print(x)
>>>
>>>
>>> print(myfunction.__name__)
myfunction
>>>
>>> print(myfunction.__doc__)
myfunction docstring

5.14.2. Cached Property

  • from functools import cached_property

  • @cached_property(method)

>>> import statistics
>>> from functools import cached_property
>>>
>>>
>>> class Iris:
...     def __init__(self, *args):
...         self._measurements = args
...
...     @cached_property
...     def mean(self):
...         return statistics.mean(self._measurements)
...
...     @cached_property
...     def stdev(self):
...         return statistics.stdev(self._measurements)
>>>
>>>
>>> flower = Iris(5.1, 3.5, 1.4, 0.2)
>>>
>>> flower.stdev
2.1794494717703365
>>>
>>> flower.mean
2.55

5.14.3. LRU (least recently used) cache

  • from functools import lru_cache

  • @lru_cache(maxsize=None)

>>> from functools import lru_cache
>>>
>>>
>>> @lru_cache(maxsize=None)
... def fib(n):
...     if n < 2:
...         return n
...     return fib(n-1) + fib(n-2)
>>>
>>>
>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
>>>
>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

5.14.4. Assignments

Code 5.84. Solution
"""
* Assignment: Decorator Functools Func
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Use `functools.wraps` in correct place
    2. Run doctests - all must succeed

Polish:
    1. Użyj `functools.wraps` w odpowiednim miejscu
    2. 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 function as an argument'

    >>> @mydecorator
    ... def hello():
    ...     '''Hello Docstring'''

    >>> hello.__name__
    'hello'
    >>> hello.__doc__
    'Hello Docstring'
"""

from functools import wraps


# type: Callable[[Callable], Callable]
def mydecorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper


Code 5.85. Solution
"""
* Assignment: Decorator Functools Args
* Complexity: easy
* Lines of code: 1 lines
* Time: 2 min

English:
    1. Use `functools.wraps` in correct place
    2. Run doctests - all must succeed

Polish:
    1. Użyj `functools.wraps` w odpowiednim miejscu
    2. 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(True)), \
    'mydecorator() should take one positional argument'

    >>> assert isfunction(mydecorator(happy=True)), \
    'mydecorator() should take one keyword argument'

    >>> assert isfunction(mydecorator(happy=True)(lambda: ...)), \
    'The result of mydecorator() should take function as an argument'

    >>> @mydecorator(happy=False)
    ... def hello():
    ...     '''Hello Docstring'''
    >>> hello.__name__
    'hello'
    >>> hello.__doc__
    'Hello Docstring'
"""

from functools import wraps


# type: Callable[[bool], Callable]
def mydecorator(happy=True):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        return wrapper

    return decorator


Code 5.86. Solution
"""
* Assignment: Decorator Functools Cls
* Complexity: easy
* Lines of code: 2 lines
* Time: 3 min

English:
    1. Modify code to restore docstring and name from decorated class
    2. Run doctests - all must succeed

Polish:
    1. Zmodyfikuj kod aby przywrócić docstring oraz nazwę z dekorowanej klasy
    2. Uruchom doctesty - wszystkie muszą się powieść

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

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

    >>> assert mydecorator(object), \
    'mydecorator() should take class as an argument'

    >>> assert isclass(mydecorator(object)), \
    'The result of mydecorator() should be a class'

    >>> @mydecorator
    ... class Hello:
    ...     '''Hello Docstring'''
    >>> hello = Hello()
    >>> hello.__name__
    'Hello'
    >>> hello.__doc__
    'Hello Docstring'
"""


# type: Callable[[Type], Type]
def mydecorator(cls):
    class Wrapper(cls):
        pass

    return Wrapper