Python decorators

38
Python decorators 21 May 2015 with @pybcn https://github.com/theblackboxio/python-decorators-demo

Transcript of Python decorators

Page 1: Python decorators

Python decorators

21 May 2015 with @pybcnhttps://github.com/theblackboxio/python-decorators-demo

Page 2: Python decorators

DisclaimerThis speaker belongs to Java world, so do not believe me.

Guillermo Blasco Jiménezes.linkedin.com/in/guillermoblascojimenez

@theblackboxio

Page 3: Python decorators

Motivationdef bar():

# preprocess things# execute bar logic# postprocess thingsreturn # value

def foo():# preprocess things# execute foo logic# postprocess thingsreturn # value

Page 4: Python decorators

Motivationdef bar():

# preprocess things# execute bar logic# postprocess thingsreturn # value

def foo():# preprocess things# execute foo logic# postprocess thingsreturn # value

Often the same processing, therefore we have code duplicated

Page 5: Python decorators

Motivation by example: cachedef hardStuff1(x):

if x in cache:return cache[x]

else:v = # bar logiccache[x] = vreturn v

def hardStuff2(x):if x in cache:

return cache[x]else:

v = # foo logiccache[x] = vreturn v

Page 6: Python decorators

Motivation by example: transactionsdef transaction1():

tx = startTransaction()try:

# execute bar logictx.commit()

except:tx.rollback()

return # value

def transaction2():tx = startTransaction()try:

# execute foo logictx.commit()

except:tx.rollback()

return # value

Page 7: Python decorators

Motivation by examples● Logging● Retry● Wrap exceptions● Deprecation warnings● Pre/post conditions● Access authorization● Profiling

Page 8: Python decorators

Decorators basicsObjectives:● isolate shared code● do not change the way to call our function

Page 9: Python decorators

Decorators basics: code isolationdef executeWithCache(x, hardStuffLogic):

if x in cache:return cache[x]

else:v = hardStuffLogic(x)cache[x] = vreturn v

executeWithCache(5, fibonacci) # before was: fibonacci(5)executeWithCache(5, anotherCostlyFunction)

Function Reference!

Page 10: Python decorators

Decorators basics: preserve callingdef executeWithCache(hardStuffLogic):

def wrapper(x):if x in cache:

return cache[x]else:

v = hardStuffLogic(x)cache[x] = vreturn v

return wrapper

fibonacci = executeWithCache(fibonacci)anoherCostlyFunction = executeWithCache(anotherCostlyFunction)

Yes, a function that returns a function

Page 11: Python decorators

Decorators basics: syntactic sugardef fibonacci(x):

#...

fibonacci = executeWithCache(fibonacci)

@executeWithCachedef fibonacci(x):

# ...

Since Python 2.4

Page 12: Python decorators

TheoryDecorator pattern is about adding functionality to objects without change the functionality of others.Python decorators implements decorator pattern applied to functions (that are objects in Python, by the way)

Page 13: Python decorators

Ramifications● Decorators with parameters● Decorators as functions or classes● Nesting decorators● Decorators applied to classes● Preservation of function metadata● Testing decorators

Page 14: Python decorators

Decorators with parameters I@precondition(lambda x: x >= 0)def fibonacci(x):

Page 15: Python decorators

Decorators with parameters IIdef precondition(precond):

def wrapper1(f):def wrapper2(x):

if not precond(x):raise ValueError('Precondition failed')

return f(x)return wrapper2

return wrapper1

Yes! A function that returns a function that returns a function

Page 16: Python decorators

Decorators as classes IOne clear problem with decorators: too many function nestingdef precondition(precond):

def wrapper1(f):def wrapper2(x):

if not precond(x):raise ValueError('Precondition failed')

return f(x)return wrapper2

return wrapper1

Agree with me that this is ugly

Page 17: Python decorators

Decorators as classes IIActually you just need a callable object that returns a function to use it as decorator. Python has three callable objects:● Functions● Classes (to build object instances)● Objects with __call__

Page 18: Python decorators

Decorators as classes IIIclass Precondition:

def __init__(self, checkFunction):self.p = checkFunction

def __call__(self, f):def wrapper(x):

if not self.p(x):raise ValueError()

return f(x)return wrapper

This makes the object instance a callable object

@Precondition(lambda x: x >= 0)def fibonacci(x):

Page 19: Python decorators

Decorators as classes IV# create new instance of decoratordec = Precondition(lambda x: x>=0) # apply as decoratorfibonacci = dec(fibonacci)# the wrapper that “dec” returns is calledfibonacci(5)

Page 20: Python decorators

Nesting decorators@cache@transaction@notNullInputdef bar(x):

...

This is great!

Page 21: Python decorators

Nesting decorators: first problem I

@memoryCache@localFileCache@hdfsCache@transaction@notNullInput@notNullOutputdef bar(x):

...

Do not abuse, please

Page 22: Python decorators

Nesting decorators: first problem II

# Make decorators more generics, do not forget OOP@Cache(“memory”,”localFile”,”hdfs”)@transaction@notNull # checks input and outputdef bar(x):

...

Page 23: Python decorators

Nesting decorators: second problem I

@cache@transaction@notNullInputdef bar(x):

...

Order matters!

Page 24: Python decorators

Nesting decorators: second problem II

@cache # executed first@transaction # executed second@notNullInput # executed thirddef bar(x):

... Why to cache and start a transaction for a possible invalid computation?

Page 25: Python decorators

Nesting decorators: second problem III

@notNullInput # executed first@cache # executed second@transaction # executed thirddef bar(x):

...

Page 26: Python decorators

Decorators applied to classes I

Decorators applied to functions, are callables that takes a function and at the end returns a function.Decorators applied to classes, are callables that takes a class and at the end returns a class.

Page 27: Python decorators

Decorators applied to classes II

# Prints a warning every time# a method is called on class instance@deprecatedclass OldClass:

Page 28: Python decorators

Decorators applied to classes III

def deprecated(clazz):class DeprecatedClass:

# Black sorcery to bypass class# methods dynamically

return DeprecatedClass

Page 29: Python decorators

Decorators applied to classes IV

● Do not replace class inheritance with class decorators● Hard to find actual use cases● Hard to deal with dynamically generated classes● Hard to deal with bound functions with function

decorators● Related to metaclasses and metaprogramming

Page 30: Python decorators

Preservation of function metadata I>>> def foo():

…>>> print foo.__name__foo

>>> @cache def foo():

…>>> print foo.__name__cache

Page 31: Python decorators

Preservation of function metadata II

Metadata loss:● __name__ and __doc__● argument specification● source code inspectionImplies that:● Harder to debug● Harder to profile● Unreadable stack traces

Page 32: Python decorators

Preservation of function metadata III

Use:● functools.wraps for function decorators● functools.update_wrapper for class decs.More info:Graham Dumpleton - How you implemented your Python decorator is wronghttp://blog.dscpl.com.au/2014/01/how-you-implemented-your-python.html

Page 33: Python decorators

Preservation of function metadata IVimport functools

def function_wrapper(wrapped):

@functools.wraps(wrapped) def _wrapper(*args, **kwargs):

return wrapped(*args, **kwargs)

return _wrapper

import functools class function_wrapper(object): def __init__(self, wrapped): self.wrapped = wrapped functools.update_wrapper(self, wrapped) def __call__(self, *args, **kwargs): return self.wrapped(*args, **kwargs)

Page 34: Python decorators

Testing decorators Idef testCache():

times = 0def mock(x):

times = times + 1mock = cache(mock)mock(1)assert times == 1mock(1)assert times == 1

Use the old fashioned way to use decorators as simply function wrappers.Use the wrapped function to check the state of the decorator instance

Page 35: Python decorators

Decorators in real world● Python itself (@staticmethod)● https://wiki.python.org/moin/PythonDecoratorLibrary● Django (@permission_required)

Page 36: Python decorators

How is this possible?● Python functions are objects● Python is a dynamic language, i.e. your code

can generate more code

Page 37: Python decorators

There is something like decorators?

Well, yes. It is named Aspect Oriented Programming (AOP). The problem is that if your language does not support dynamic code and/or your functions are not objects then you have to cook manually decorators or use an AOP framework (proxy classes nightmare).

Page 38: Python decorators

Guillermo Blasco Jiménez

Thanks!https://github.com/theblackboxio/python-decorators-demo

Questions?