Decorators Pycon
https://www.youtube.com/watch?v=MjHpMCIvwsY
Slides and Code Snippets can be downloaded here
-
Decorating a function creates three callables - the decorated function, the decorator, and the return value (function) from the decorator function assigned back to the decorated function.
-
Scope resolution in python follows the LEGB rule, looking for variables at each level before moving upwards. Source:
-
Local(L): Defined inside function/class
-
Enclosed(E): Defined inside enclosing functions(Nested function concept)
-
Global(G): Defined at the uppermost level
-
Built-in(B): Reserved names in Python built-in modules
-
-
The decorator function (the one that you apply to others with e.g.
@decorator) executes once, when you decorate a function, but the inner wrapper function gets executed every time function is called. -
Since the outside function is only executed once, you can use it to define additional functionality using
nonlocal.
Snippet from Lecture:
import time
class CalledTooOftenError(Exception):
pass
def once_per_minute(func):
last_invoked = 0
def wrapper(*args, **kwargs):
nonlocal last_invoked
elapsed_time = time.time() - last_invoked
if elapsed_time < 60:
raise CalledTooOftenError(f"Only {elapsed_time} has passed")
last_invoked = time.time()
return func(*args, **kwargs)
return wrapper
if __name__ == '__main__':
@once_per_minute
def add(a, b):
return a + b
print(add(2, 2))
print(add(3, 3))
-
nonlocalupdates the variable in the enclosing scope, without having to make global variables. You only need to usenonlocalif you are assigning to the variable in the inner scope; if you’re accessing it follows LEGB scope. -
Adding a decorator to a function passes the function as the first argument to the decorator. If arguments need to be passed to a decorator, you need to define an additional wrapper function that receives those arguments:
def some_decorator(n):
def middle(func): # func is function being decorated
function_specific_local = 0
def wrapper(*args, **kwargs):
nonlocal function_specific_local
function_specific_local += n
return func(*args, **kwargs)
return wrapper
return middle
- This also increases the number of callables to 4. Applying this to a function manually would look like:
def my_function():
...
my_function = some_decorator(n=5)(my_function)
With a decorator:
@some_decorator(n=5)
def my_function()
...
- Memoization wrappers:
argsin wrapper functions are defined as a tuple. As tuples are hashable, you can check if the sameargshave been passed before to memoize. If some of the arguments aren’t hashable, you canpickleargsandkwargsbefore comparing it against your cache:
key = (pickle.dumps(args), pickle.dump(kwargs))
- If you have distinct classes, but want similar behavior, instead of using inheritance/multiple inheritance, or setting class attributes after the fact, you can use a decorator. Since classes are callables, this is no different.
Snippet from Lecture:
def fancy_repr(self):
return f"I'm a {type(self).__name__}, with vars {vars(self)}"
def repr_and_birthday(c):
c.__repr__ = fancy_repr
def wrapper(*args, **kwargs):
o = c(*args, **kwargs)
o._created_at = time.time()
return o
return wrapper
if __name__ == '__main__':
@repr_and_birthday
class Foo():
def __init__(self, x, y):
self.x = x
self.y = y
f = Foo(10, [10, 20, 30])
print(f)
print(f._created_at)
-
Use decorators instead of metaclasses if possible.
-
Wrapping your inner wrapper function (the one with
*argsand**kwargs) withfunctools.wrapsallows you to keep attributes of a function (e.g.__name__,__doc__) -
functools.partialsacts as a wrapper function that passes*argsor**kwargsto a function
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
assert square(2) == 4
https://www.youtube.com/watch?v=0LPuG825eAk
pdb notes
- up - goes up a frame
- down - goes down a frame
- p - prints a variable
- pp - pretty prints a variable
- useful to use
locals()to see all variables in a frame listorll- lists the code around the current linen- next lines- step into function, if there’s a function call on the current linereturnorr- returns from the current function, useful in case you just stepped into a function and want to return to the caller
can set PYTHONBREAKPOINT=0 to disable all breakpoints
If you want to breakpoint when a crash happens in your program, you can use python3 -m pdb file.py, then hit c to continue. When a fatal error happens, you’ll be dropped into the debugger.
If the exception that causes the crash is caught/pretty-printed, you can use import pdb; pdb.post_mortem() to drop into the debugger in some higher stack frame.