In Python, decorators are one of the most misunderstood features and one of the most blogged topic, due to the their gotcha nature.
What’s a gotcha: > In programming, a gotcha is a feature of a system, a program or a programming language that works in the way it is > documented but is counter-intuitive and almost invites mistakes because it is both enticingly easy to invoke and > completely unexpected and/or unreasonable in its outcome.
If you’re not familiar with the idea behind decorators, head to PEP-0318. It’ll help understand and be able to write simple decorators.
However, some of the gotchas I’ve faced are:
How do I pass args and kwargs to the decorator?
The obvious approach (not working)
```python def exposed(func, attrs_to_expose): def _callable(args, *kwargs): for attr in attrs_to_expose: print(getattr(func, attr)) return func(args, **kwargs) return _callable
@exposed(‘name’, ‘name’) def decorated_fn(*args): ….
decorated_fn(‘1’, ‘2’, ‘3’) decorated_func main …. ```
And the naive working non-obvious at first approach
```python def exposed(attrs_to_expose): def decorator(func): def _callable(args, *kwargs): for attr in attrs_to_expose: print(getattr(func, attr)) return func(args, **kwargs) return _callable return decorator
decorated_fn(‘1’, ‘2’, ‘3’) totally_working main yep ```
Same idea carries over for kwargs. BUT PLEASE, don’t use
getattr like that. I mention the
functools.wraps bug and how Django devs solved it.
Why do we use functools.wraps? Mainly legacy
To make debugging decorators way easier. It updates function attributes mentioned in
functools.WRAPPER_ASSIGNMENTS, which at the time of writing this are
'__module__', '__name__', '__qualname__', '__doc__',
In code for earlier than Python 2.7:
```python def exposed(func): def _callable(args, **kwargs): print(func.name) print(func.doc) return func(args, **kwargs) return _callable
When we use it on:
@exposed def totally_working(*args, **kwargs): “"”I lied””” raise Exception
In Python versions earlier than 2.7
totally_working(‘d’, 3, ‘c’, 0, ‘rators’) # nothing printed
In Python versions 2.7.8 or and 3.3 and newer
totally_working(‘d’, 3, ‘c’, 0, ‘rators’) totally_working I lied ```
Why do we implement our own
functools.wraps solutions? Again legacy
Nothing is without bugs and
functools.wraps had a pretty long lasting bug that was reported http://bugs.python.org/issue3445 and fixed way later in Python3.3 and later.
functools.WRAPPER_ASSIGNMENTS at that time was aggressively trying to update a function’s attributes, not taking into consideration the chances of them not existing.
The issue had Python developers think about ways to solve it. And from my limited experience, Django developers have the most elegant workaround. As seen https://github.com/django/django/blob/master/django/utils/decorators.py#L82