{"id":224,"date":"2024-04-15T14:34:47","date_gmt":"2024-04-15T13:34:47","guid":{"rendered":"https:\/\/mrzebra.co.uk\/code\/?p=224"},"modified":"2024-04-15T14:55:11","modified_gmt":"2024-04-15T13:55:11","slug":"decorators-in-python","status":"publish","type":"post","link":"https:\/\/zebra-north.com\/code\/2024\/04\/15\/decorators-in-python\/","title":{"rendered":"Decorators in Python"},"content":{"rendered":"\n<p>Python&#8217;s documentation isn&#8217;t entirely clear on what a decorator is or how it works, so here are the details.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">What Is a Decorator?<\/h2>\n\n\n\n<p>A <strong>decorator<\/strong> is a way to apply a <strong>wrapper<\/strong> around function.  It&#8217;s given the name &#8220;decorator&#8221; because of the way you attach it to the function being wrapped: like a decoration.<\/p>\n\n\n\n<p>The decorator itself does not actually wrap the function. A better name for it might be a <strong>wrapper factory<\/strong>.<\/p>\n\n\n\n<p>Python unfortunately refers to both &#8220;wrapper factories&#8221; and &#8220;wrapper-factory factories&#8221; as &#8220;decorators&#8221;, which is quite confusing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">An Aside: Closures<\/h2>\n\n\n\n<p>Closures are important to the workings of the decorator.  If you already know what a closure is, skip to the next heading.  If not, here is a brief explanation.<\/p>\n\n\n\n<p>A closure allows you to create a function and have it reference variables from its enclosing scope later, when it is executed.  Consider the code below:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">def create_function(message):\n\n    def say():\n        print(message)\n\n   return say\n\nsay_hello = create_function(&quot;hello&quot;)\n\n# Print &quot;hello&quot;.\nsay_hello()<\/code><\/pre>\n\n\n\n<p>The <code>say()<\/code> function is keeping a reference to the <code>message<\/code> variable, even after the <code>create_function<\/code> function has returned.<\/p>\n\n\n\n<p>The function <code>say()<\/code> is a <em>closure<\/em> that <em>closes around<\/em> the <code>message<\/code> variable.<\/p>\n\n\n\n<p>It&#8217;s equivalent to a class that does this:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">class Say:\n    __init__(message):\n        self.message = message\n\n    __call__()\n        print(self.message)\n\nsay_hello = Say(&quot;hello&quot;)\n\n# Print &quot;hello&quot;.\nsay_hello()<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Starting Point: A Simple Wrapper<\/h2>\n\n\n\n<p>Suppose you wish to wrap <code>my_func()<\/code> such that it prints a message whenever it is called.  This can be achieved by creating a wrapper function that calls <code>my_func()<\/code>, and then reassigning the variable <code>my_func<\/code> so it actually points to the wrapper function:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\"># The function to be wrapped.\ndef my_func():\n    print(&#039;Doing some work...&#039;)\n\n# The wrapper.\ndef wrapper():\n    print(&#039;Hello world.&#039;)\n    my_func()\n\n# Make it so that calling my_func() actually calls wrapper().\nmy_func = wrapper\n\n# This will print &quot;Hello world. Doing some work...&quot;.\nmy_func()<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Improvement: A Wrapper Factory<\/h2>\n\n\n\n<p>The code above achieves the goal, but it has one flaw: Because it calls <code>my_func()<\/code> directly, the wrapper can only ever wrap <code>my_func()<\/code>. It would be nice if we could wrap <em>any<\/em> function in our wrapper.<\/p>\n\n\n\n<p>To achieve this we will create a <strong>factory function<\/strong> that returns the wrapper. The function to be wrapped is passed in as a parameter and the wrapper function closes around it, storing a reference to it.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">def wrapper_factory(func):\n\n    def wrapper():\n        print(&#039;Hello world.&#039;)\n        return func()\n\n    return wrapper\n\nmy_func = wrapper_factory(my_func)\nmy_other_func = wrapper_factory(my_other_func)<\/code><\/pre>\n\n\n\n<p>This will now print &#8220;Hello world.&#8221; before any wrapped function.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Syntactic Sugar: @decorator<\/h2>\n\n\n\n<p>The <code>@decorator<\/code> syntax in Python is a simple syntax for the above: It runs the wrapper factory and replaces the function with the wrapped function.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">def wrapper_factory(func):\n\n    def wrapper():\n        print(&#039;Hello world.&#039;)\n        return func()\n\n    return wrapper\n\n@wrapper_factory\ndef my_func():\n    print(&#039;Doing some work...&#039;)   <\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Customizing the Decorator: A Wrapper-Factory-Factory<\/h2>\n\n\n\n<p>Suppose now that you wish to customize the wrapper function for each function that you wrap.  This is achieved by creating a factory function that returns a wrapper factory.  This allows you to pass variables in to the wrapper-factory-factory and have the wrapper-factory or the wrapper close around them.<\/p>\n\n\n\n<p>For example, suppose you wish to customize the message printed by the wrapper:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">def wrapper_factory_factory(message):\n\n    def wrapper_factory(func):\n\n        def wrapper():\n            print(message)\n            return func()\n\n        return wrapper\n\n    return wrapper_factory\n\n@wrapper_factory_factory(&#039;HELLO&#039;)\ndef my_func():\n    print(&#039;Doing some work...&#039;)\n\n# This will print &quot;HELLO Doing some work...&quot;\nmy_func()<\/code><\/pre>\n\n\n\n<p>This is starting to get a little complicated, so lets go through it step by step:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>wrapper_factory_factory(&#039;HELLO&#039;)<\/code> is called.<br>This closes around the <code>message<\/code> variable and returns a <code>wrapper_factory<\/code> function.<\/li>\n\n\n\n<li><code>wrapper_factory(my_func)<\/code> is called.<br>This closes around the <code>func<\/code> variable and returns a <code>wrapper<\/code> function.<\/li>\n\n\n\n<li><code>my_func<\/code> is replaced by the <code>wrapper<\/code> function returned from step 2.<\/li>\n\n\n\n<li>The code calls <code>my_func()<\/code>, which actually calls <code>wrapper()<\/code>.<br><code>wrapper()<\/code> prints the message &#8220;HELLO&#8221;, then calls the original <code>my_func()<\/code> function.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Decorator or Decorator Factory?<\/h2>\n\n\n\n<p>If you are working with existing code, how do you know if you are working with a decorator or a decorator factory?<\/p>\n\n\n\n<p>A decorator will not have brackets (parentheses) following it.  A decorator factory will.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\"># No brackets: It is a decorator and returns a wrapper.\n@decorator1\n\n# Brackets: It is a decorator factory and returns a decorator.\n@decorator2()<\/code><\/pre>\n\n\n\n<p>If you are creating your own decorator then:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The wrapper is always the same: Use a decorator.<\/li>\n\n\n\n<li>The wrapper needs to be customized for each function it wraps: Use a decorator factory.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Parameters and Return Values<\/h2>\n\n\n\n<p>There are two ways to pass parameters through your wrapper to the wrapped function.  If the functions you are wrapping will always have the same parameters then you can just copy them.  Otherwise, you can use Python&#8217;s variable-parameters syntax.  The return value of the wrapped function can be returned directly from the wrapper.<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-\">def decorator(func):\n\n    def wrapper(*args, **kwargs):\n        return func(*args, **kwargs)\n\n@decorator\ndef my_func(... any parameters ...):\n    # ...<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Decorators That Aren&#8217;t Wrappers<\/h2>\n\n\n\n<p>One final pattern that you may encounter is to use a decorator to do some work when the function is defined.  The decorator then returns the function directly, instead of returning a wrapper around it.<\/p>\n\n\n\n<p>For example, suppose you wish to add your function to a list of commands that users can invoke:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-python\">commands = []\n\ndef command_decorator(func):\n    commands.append(func.__name__)\n\n    return func\n\n@command_decorator\ndef help():\n    # Do some worker...<\/code><\/pre>\n\n\n\n<p>The code above will add &#8220;help&#8221; to the <code>commands<\/code> list.  It uses Python&#8217;s built-in <code>__name__<\/code> property to get the function&#8217;s name.<\/p>\n\n\n\n<p>Note that this happens when the <code>help()<\/code> function is defined, not when it is called.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Python&#8217;s documentation isn&#8217;t entirely clear on what a decorator is or how it works, so here are the details.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[33],"tags":[],"class_list":["post-224","post","type-post","status-publish","format-standard","hentry","category-python"],"_links":{"self":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/224","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/comments?post=224"}],"version-history":[{"count":10,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/224\/revisions"}],"predecessor-version":[{"id":235,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/posts\/224\/revisions\/235"}],"wp:attachment":[{"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/media?parent=224"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/categories?post=224"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/zebra-north.com\/code\/wp-json\/wp\/v2\/tags?post=224"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}