Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Decorators in Python

1,991 views

Published on

Published in: Technology, News & Politics

Decorators in Python

  1. 1. Decorators in Python <ul><li>First look at a decorator
  2. 2. Defining a decorator
  3. 3. Callable objects
  4. 4. In the wild
  5. 5. Possible drawbacks
  6. 6. Avoiding the drawbacks
  7. 7. Alternatives to decorators
  8. 8. Final thoughts </li></ul>
  9. 9. First look at a decorator <ul><li>Example of decorator usage:
  10. 10. from myapp.decorators import spam
  11. 11. @spam
  12. 12. def sketch(character, line):
  13. 13. print &quot;%s says: %s&quot; % (character, line) </li></ul>
  14. 14. First look at a decorator <ul><li>What happens when we call our sketch function?
  15. 15. >>> sketch(&quot;Wife&quot;, &quot;Have you got anything without spam?&quot;)
  16. 16. Wife says: Have you got anything without spam?
  17. 17. Vikings sing: Spam spam spam spam! </li></ul>
  18. 18. First look at a decorator <ul><li>What's going on there?
  19. 19. Did the decorator add this part?
  20. 20. Wife says: Have you got anything without spam?
  21. 21. Vikings sing: Spam spam spam spam!
  22. 22. The spam decorator modified the say_something function, adding stuff that wasn't in our original definition. Neat! </li></ul>
  23. 23. Defining a decorator <ul><li>How does this modification happen?
  24. 24. The @ syntax (called &quot;pie&quot; syntax) is syntactic sugar for this:
  25. 25. from myapp.decorators import spam
  26. 26. def sketch(character, line):
  27. 27. print &quot;%s says: %s&quot; % (character, line)
  28. 28. sketch = spam(sketch) </li></ul>
  29. 29. Defining a decorator <ul><li>The decorator looks like a function there!
  30. 30. Guess what? It is. Here's how our spam decorator is defined:
  31. 31. # In myapp/decorators.py
  32. 32. def spam(callable):
  33. 33. def decorated_callable(*args, **kwargs):
  34. 34. result = callable(*args, **kwargs)
  35. 35. print &quot;Vikings sing: Spam spam spam spam!&quot;
  36. 36. return result
  37. 37. return decorated_callable </li></ul>
  38. 38. Defining a decorator <ul><li>In general, a decorator is a callable object which can be called with another callable object as a parameter, to produce yet another callable object.
  39. 39. (Pause to take in that definition)
  40. 40. What's a callable object? </li></ul>
  41. 41. Callable objects (quick revision) <ul><li>Most obviously, functions and methods are callable. As with everything in Python, they're objects.
  42. 42. In Python, classes are also callable; we call them to create instances:
  43. 43. spam = Spam()
  44. 44. Also, any instance which has a __call__ method is callable
  45. 45. We can use pie-decorator syntax above function, method and class definitions </li></ul>
  46. 46. Defining a decorator <ul><li>Here's another way we could define spam :
  47. 47. class spam(object):
  48. 48. def __init__(self, callable):
  49. 49. self.c = callable
  50. 50. def __call__(self, *args, **kwargs):
  51. 51. result = self.c(*args, **kwargs)
  52. 52. print &quot;Vikings sing: Spam spam spam spam!&quot;
  53. 53. return result
  54. 54. Applying the decorator replaces our callable with an instance of spam </li></ul>
  55. 55. In the wild <ul><li>Let's look at some useful decorators!
  56. 56. We use them to control user access and transaction strategies on Django view functions
  57. 57. We use decorators from the Celery library to turn a regular function into an asynchronously executable task </li></ul>
  58. 58. In the wild <ul><li>User access control in a Django app:
  59. 59. # In views.py
  60. 60. from django.contrib.auth.decorators import login_required
  61. 61. @login_required
  62. 62. def index(request):
  63. 63. return render_to_response('index.html', {}) </li></ul>
  64. 64. In the wild <ul><li>Transaction strategies in a Django app:
  65. 65. from django.db import transaction
  66. 66. @transaction.commit_on_success
  67. 67. def make_breakfast(request):
  68. 68. egg = BoiledEgg.objects.create(soft_boiled=True)
  69. 69. # Transaction implicitly started
  70. 70. if not Toast.objects.count():
  71. 71. raise OutOfToastError # Unhandled exception!
  72. 72. # Transaction implicitly rolled back
  73. 73. return render_to_response('breakfast.html', {'egg': egg})
  74. 74. # Transaction implicitly committed </li></ul>
  75. 75. In the wild <ul><li>Creating asynchronous tasks with Celery:
  76. 76. from celery.decorators import task
  77. 77. @task
  78. 78. def add(x, y):
  79. 79. return x + y
  80. 80. >>> result = add. delay (8, 8)
  81. 81. >>> result. wait () # Wait for celery daemon to compute result
  82. 82. 16 </li></ul>
  83. 83. Possible drawbacks <ul><li>Decorators are great lightweight way of adding reusable behaviour to your code, but beware!
  84. 84. Our spam decorator hides the original function, making it impossible to unit test without the behaviour of the decorator
  85. 85. This is bad if your decorator introduces tight coupling to a dependency
  86. 86. This could be managed with global behaviour configuration for your decorators. Eww, global state! </li></ul>
  87. 87. Avoiding (some of) the drawbacks <ul><li>Add to the callable instead of wrapping it
  88. 88. def spam(callable):
  89. 89. def sing(*args, **kwargs):
  90. 90. result = callable(*args, **kwargs)
  91. 91. print &quot;Vikings sing: Spam spam spam spam!&quot;
  92. 92. return result
  93. 93. callable.sing = sing
  94. 94. return callable
  95. 95. >>> sketch. sing (&quot;Wife&quot;, &quot;Have you got anything that isn't spam?&quot;)
  96. 96. Wife says: Have you got anything that isn't spam?
  97. 97. Vikings sing: Spam spam spam spam! </li></ul>
  98. 98. Alternatives to decorators <ul><li>Consider whether inheritance or composition would be a better way to augment your code:
  99. 99. class BaseSketch(object):
  100. 100. def __init__(self, character, line):
  101. 101. print &quot;%s says: %s&quot; % (character, line)
  102. 102. class SpamSketch(BaseSketch):
  103. 103. def __init__(self, *args):
  104. 104. super(SpamSketch, self).__init__(*args)
  105. 105. print &quot;Vikings sing: Spam spam spam spam!&quot; </li></ul>
  106. 106. Final thoughts <ul><li>Decorators are an awesomely powerful way to configure stuff with useful, defined behaviours
  107. 107. But we already have OO constructs to do this... for example MI allows us to use mixin classes
  108. 108. Using mixins would mean everything has to be a class... Eek, memories of Java
  109. 109. Libraries (e.g. Celery) are tending to provide both class-based and decorator interfaces in their APIs
  110. 110. When you have such a choice, you can use decorators to quickly strap the library onto your code
  111. 111. Then judge whether you should refactor to use classes </li></ul>

×