python decorators

python decorators

An easy introduction to python decorators (this deck is mostly code examples). Presented to the Memphis Python User group, December 2014.

D57aec10399cbb252bd890c2bb3fe1c9?s=128

Brad Montgomery

December 15, 2014
Tweet

Transcript

  1. decorators

  2. Use python 3!

  3. What is a Decorator?

  4. # An example from Flask @app.route("/hello") def some_view(): return "Hello

    World!"
  5. # An example from Flask @app.route("/hello") def some_view(): return "Hello

    World!"
  6. # An example from Django @login_required def some_view(request): # ...

    return HttpResponse(content)
  7. # An example from Django @login_required def some_view(request): # ...

    return HttpResponse(content)
  8. class MyThing: @property def value(self): return self.calculate_value() def calculate_value(self): #

    do some calculations here >>> thing = MyThing() >>> thing.value 42
  9. class MyThing: @property def value(self): return self.calculate_value() def calculate_value(self): #

    do some calculations here >>> thing = MyThing() >>> thing.value 42
  10. class MyThing: @property def value(self): return self.calculate_value() def calculate_value(self): #

    do some calculations here >>> thing = MyThing() >>> thing.value 42
  11. But First!
 Did you know…

  12. # Generalized arguments def foo(*args, **kwargs): print("args: {}".format(args)) print("kwargs: {}".format(kwargs))

  13. # Generalized arguments def foo(*args, **kwargs): print("args: {}".format(args)) print("kwargs: {}".format(kwargs))

  14. # Generalized arguments def foo(*args, **kwargs): print("args: {}".format(args)) print("kwargs: {}".format(kwargs))

    >>> foo('a single arg') # args: ('a single arg',) # kwargs: {}
  15. # Generalized arguments def foo(*args, **kwargs): print("args: {}".format(args)) print("kwargs: {}".format(kwargs))

    >>> foo('a single arg') # args: ('a single arg',) # kwargs: {} >>> foo('arg1', 'arg2', kw1='keyword1', kw2='keyword2') # args: ('arg1', 'arg2') # kwargs: {'kw1': ‘keyword1', ‘kw2': 'keyword2'}
  16. explicit > implicit

  17. # You can put functions inside of functions? def outside():

    name = "i'm outside!" def inside(): print(name) inside()
  18. # You can put functions inside of functions? def outside():

    name = "i'm outside!" def inside(): print(name) inside() >>> outside() # i'm outside!
  19. # Functions are fist-class citizens? def citizen_func(): pass >>> citizen_func.__class__

    # '<class 'function'>' >>> issubclass(citizen_func.__class__, object) # True
  20. # Functions are fist-class citizens? def citizen_func(): pass >>> citizen_func.__class__

    # '<class 'function'>' >>> issubclass(citizen_func.__class__, object) # True
  21. # Functions are fist-class citizens? def citizen_func(): pass >>> citizen_func.__class__

    # '<class 'function'>' >>> issubclass(citizen_func.__class__, object) # True
  22. # You can pass functions as # arguments to functions:

    def callme(msg): print(msg) def carly(f, y): f(y) carly(callme, "maybe") # maybe
  23. # You can pass functions as # arguments to functions:

    def callme(msg): print(msg) def carly(f, y): f(y) carly(callme, "maybe") # maybe
  24. # You can pass functions as # arguments to functions:

    def callme(msg): print(msg) def carly(f, y): f(y) carly(callme, "maybe") # maybe
  25. # You can pass functions as # arguments to functions:

    def callme(msg): print(msg) def carly(f, y): f(y) carly(callme, "maybe") # maybe
  26. # A Holiday decorator! def holiday(f): def wrapper(*args, **kwargs): result

    = f(*args, **kwargs) return "‚ {0} ‚".format(result) return wrapper
  27. # A Holiday decorator! def holiday(f): def wrapper(*args, **kwargs): result

    = f(*args, **kwargs) return "‚ {0} ‚".format(result) return wrapper @holiday def happy(): return "Happy Holidays!” # >>> happy() # “‚ Happy Holidays! ‚”
  28. # A Holiday decorator! def holiday(f): def wrapper(*args, **kwargs): result

    = f(*args, **kwargs) return "‚ {0} ‚".format(result) return wrapper @holiday def happy(): return "Happy Holidays!” # >>> happy() # “‚ Happy Holidays! ‚”
  29. # A simple, DIY timing decorator import time def timed(func):

    """Decorator that prints time info.""" def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
  30. # A simple, DIY timing decorator import time def timed(func):

    """Decorator that prints time info.""" def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
  31. # A simple, DIY timing decorator import time def timed(func):

    """Decorator that prints time info.""" def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
  32. # A simple, DIY timing decorator import time def timed(func):

    """Decorator that prints time info.""" def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
  33. # apply the decorator @timed def foo(): """A Foo function."""

    print("Foo.")
  34. # apply the decorator @timed def foo(): """A Foo function."""

    print("Foo.")
  35. # Or using the old-style syntax. def foo(): """A Foo

    function.""" print("Foo.") old_foo = timed(old_foo)
  36. # Or using the old-style syntax. def foo(): """A Foo

    function.""" print("Foo.") foo = timed(foo)
  37. # Problems! >>> foo.__name__ # Expected: 'foo' >>> foo.__doc__ #

    Should show docstring
  38. # Problems! >>> foo.__name__ # Expected: 'foo' >>> foo.__doc__ #

    Should show docstring wrapper None
  39. # Use functools.wraps to keep that meta-data! from functools import

    wraps def timed(func): """Better timing decorator.""" @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
  40. # Use functools.wraps to keep that meta-data! from functools import

    wraps def timed(func): """Better timing decorator.""" @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
  41. # Better! >>> foo.__name__ # Expected: 'foo' >>> foo.__doc__ #

    Should show docstring # AND! The original function is # in __wrapped__ >>> foo.__wrapped__()
  42. # A Practical Example. Logging function Calls. import time def

    logged(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) msg = "[{}] {} called with args: {}, kwargs: {}" print(msg.format( time.ctime(), func.__name__, args, kwargs )) return result return wrapper
  43. @logged def super_funk(*args, **kwargs): pass >>> super_funk('Blammo', kap='pow') # [Mon

    Nov 17 16:38:44 2014] super_funk called # with args: ('Blammo',), kwargs: {'kap': 'pow'}
  44. from decimal import Decimal def currency(func): @wraps(func) def wrapper(*args, **kwargs):

    result = func(*args, **kwargs) result = Decimal(str(result)) result = result.quantize(Decimal(".01")) return "$ {0}".format(result) return wrapper
  45. @currency def calculate_tax(value, rate=0.1): return value * rate >>> calculate_tax(100)))

    # “$ 10.00”
  46. # Decorators with arguments def currency(symbol='$'): def decorate(func): @wraps(func) def

    wrapper(*args, **kwargs): result = func(*args, **kwargs) result = Decimal(str(result)) result = result.quantize(Decimal(".01")) return "{} {}".format(symbol, result) return wrapper return decorate
  47. # Decorators with arguments def currency(symbol='$'): def decorate(func): @wraps(func) def

    wrapper(*args, **kwargs): result = func(*args, **kwargs) result = Decimal(str(result)) result = result.quantize(Decimal(".01")) return "{} {}".format(symbol, result) return wrapper return decorate
  48. # Decorators with arguments def currency(symbol='$'): def decorate(func): @wraps(func) def

    wrapper(*args, **kwargs): result = func(*args, **kwargs) result = Decimal(str(result)) result = result.quantize(Decimal(".01")) return "{} {}".format(symbol, result) return wrapper return decorate Take it another level!
  49. # Decorators with arguments def currency(symbol='$'): def decorate(func): @wraps(func) def

    wrapper(*args, **kwargs): result = func(*args, **kwargs) result = Decimal(str(result)) result = result.quantize(Decimal(".01")) return "{} {}".format(symbol, result) return wrapper return decorate
  50. @currency(symbol="£") def calculate_tax(value, rate=0.1): return value * rate >>> calculate_tax(100)

    # ‘£ 10.00’
  51. Resources # Python Cookbook, 3rd ed. # Chapter 9, Metaprogramming

    http://www.jeffknupp.com/blog/2013/11/29/improve-your-python-decorators-explained/ http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/ https://realpython.com/blog/python/primer-on-python-decorators/ http://python-3-patterns-idioms-test.readthedocs.org/en/latest/PythonDecorators.html