avatarBruce H. Cottman, Ph.D.

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

8319

Abstract

sult <span class="hljs-keyword">return</span> wrapper <span class="hljs-keyword">return</span> decorator</pre></div><div id="d1bb"><pre><span class="hljs-meta">@log_call(<span class="hljs-params">ERROR=<span class="hljs-literal">True</span></span>)</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_one</span>(<span class="hljs-params">x</span>): x = x+<span class="hljs-number">1</span> <span class="hljs-keyword">return</span>(x) y=<span class="hljs-number">0</span> y = add_one(y) y</pre></div><figure id="1863"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*tUHZvKsj79blu5Uu8ul6oA.png"><figcaption>Figure 4. The output of add_one wrapped with decorator with arguments log_call. Image: a screenshot of the author’s terminal.</figcaption></figure><p id="1699">Next, we dig into <code>@wrap</code>and poke around a bit to find out why we need it.</p><h1 id="679b">3. @wrap Retains Function Metadata</h1><p id="42ff">It is usually not necessary to retain the function object instance metadata. However, we need the function object instance reference to correctly wrap the decorator.</p><p id="bbb1">The built-in Python function <code>dir(function-symbol)</code> details the function metadata. In the following example, we use <code>dir(function-symbol)</code> to create a decorator to detail the function's metadata.</p><div id="3192"><pre><span class="hljs-keyword">def</span> <span class="hljs-title function_">bad_dir</span>(<span class="hljs-params">*a, **kw</span>): <span class="hljs-string">""</span><span class="hljs-string">" Decorator @bad_dir wraps a call of dir on the funtion inefficently. "</span><span class="hljs-string">""</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">decorator</span>(<span class="hljs-params">func</span>): <span class="hljs-variable">@wraps</span>(func) <span class="hljs-keyword">def</span> <span class="hljs-title function_">wrapper</span>(<span class="hljs-params">*args, **kwargs</span>): <span class="hljs-comment">#Pre:</span> result = dir(func) <span class="hljs-comment">#post:</span> logger.info(<span class="hljs-string">"function: {}, a:{}, kw()"</span>.format(<span class="hljs-string">'bad_dir'</span>,a, kw)) logger.info(<span class="hljs-string">"function: {}, args:{}, kwargs()"</span>.format(func.name,args, kwargs)) <span class="hljs-keyword">return</span> result <span class="hljs-keyword">return</span> wrapper <span class="hljs-keyword">return</span> decorator</pre></div><div id="8630"><pre><span class="hljs-meta">@bad_dir(<span class="hljs-params"><span class="hljs-number">1</span>, ERROR=<span class="hljs-literal">True</span></span>)</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_one</span>(<span class="hljs-params">x</span>): x = x+<span class="hljs-number">1</span> <span class="hljs-keyword">return</span>(x)</pre></div><div id="809b"><pre>y=<span class="hljs-number">0</span> <span class="hljs-function"><span class="hljs-title">add_one</span><span class="hljs-params">(y)</span></span></pre></div><figure id="3bdb"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*fZezdcQqL-dr1dqzP5jTuQ.png"><figcaption>Figure 5. The output of add_one wrapped with decorator bad_dir. Image: a screenshot of the author’s terminal.</figcaption></figure><p id="7d28">The <code>name</code> function object attribute is used by our <code>log_call</code>decorator, but you can look through the list to find other useful function object attributes.</p><p id="7bc6"><i>Note: <a href="https://stackoverflow.com/questions/308999/what-does-functools-wraps-do">@wrap</a> is in the <code>functools</code> package.</i></p><h1 id="10f6">4. Decorator With Arguments Template: Logging Uses Arguments</h1><p id="76b5">We need encapsulation code for a helper function that uses the <code>log_call</code> decorator's arguments.</p><p id="b730">What does the <code>log_output</code> function actually do in detail?</p><div id="0f2f"><pre><span class="hljs-keyword">from</span> typing import Dict, List, <span class="hljs-keyword">Any</span> def log_output(fun, <span class="hljs-keyword">result</span>:<span class="hljs-keyword">Any</span>, kw:Dict, check_state:List ) <span class="hljs-operator">-</span><span class="hljs-operator">></span> <span class="hljs-keyword">None</span>: <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> kw: keyl <span class="hljs-operator">=</span> key.<span class="hljs-built_in">lower</span>() if keyl <span class="hljs-keyword">in</span> check_state: if kw[key]: eval(<span class="hljs-string">'logger.'</span><span class="hljs-operator">+</span>keyl)("function: {}, result: {}".format(fun.name,<span class="hljs-keyword">result</span>))</pre></div><p id="7236">We next create the <code>log_call</code> decorator to accept arguments using the new decorator pattern.</p><div id="e628"><pre>def log_call(*a, **kw): <span class="hljs-string">""" Decorator @log_call wraps the funtion with log events. """</span> def decorator(<span class="hljs-function"><span class="hljs-keyword">fun</span>):</span> <span class="hljs-meta">@wraps(fun)</span> def wrapper(*args, **kwargs): #Pre: result = <span class="hljs-function"><span class="hljs-title">fun</span><span class="hljs-params">(*args, **kwargs)</span></span> #post: check_state = (<span class="hljs-string">'debug'</span>, <span class="hljs-string">'info'</span>, <span class="hljs-string">'success'</span>, <span class="hljs-string">'warning'</span>, <span class="hljs-string">'error'</span>, <span class="hljs-string">'critical'</span>) log_output(<span class="hljs-function"><span class="hljs-keyword">fun</span>, result, kw, check_state)</span> <span class="hljs-keyword">return</span> result <span class="hljs-keyword">return</span> wrapper <span class="hljs-keyword">return</span> decorator</pre></div><div id="08d5"><pre><span class="hljs-meta">@log_call(<span class="hljs-params">ERROR=<span class="hljs-literal">True</span></span>)</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">add_one</span>(<span class="hljs-params">x</span>): x = x+<span class="hljs-number">1</span> <span class="hljs-keyword">return</span>(x) y=<span class="hljs-number">0</span></pre></div><div id="cd41"><pre><span class="hljs-attribute">y</span> <span class="hljs-operator">=</span> add_one(y) y</pre></div><figure id="6277"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*3aW5jcO9ZKtz_jHV4Qqq8Q.png"><figcaption>Figure 6. The output of add_one wrapped with decorator with arguments log_call. Image: a screenshot of the author’s terminal.</figcaption></figure><p id="40b4">The output shows us that we were successful in changing the logging state with the call <code>@log_call(ERROR=True)</code>.</p><p id="3cd1">What would the output be with <code>@log_call(ERROR=True, CRITICAL=True, FAIL=True)</code>?</p><h1 id="c73a">5. Using Two Decorators: add_one</h1><p id="c68b">We turn <code>add_one</code> into a decorator that increases the global <code>call_count</code> by one.</p><div id="6968"><pre><span class="hljs-meta">#GLOBAL </span> call_count= <span class="hljs-number">0</span> def add_one(fun): """ Decorator pre-function call and post-function call of function func. """ def <span class="hljs-keyword">wrapper</span>(*args, **kwargs): <span class="hljs-keyword">global</span> call_count #Pre action result = fun(*args, **kwargs) logger.<span class="hljs-keyword">info</span>(<span class="hljs-string">'call_count:{}'</span>.format(call_count)) call_count = call_count+<span class="hljs-number">1</span> logger.<span class="hljs-keyword">info</span>(<span class="hljs-string">'Increase call_count:{}'</span>.format(call_count)) <span class="hljs-keyword">return</span>

Options

result <span class="hljs-keyword">return</span> <span class="hljs-keyword">wrapper</span></pre></div><p id="fab6">Actually, we use three decorators. <code>@wraps</code> counts as a decorator applied to the function <code>pow</code>.</p><div id="8aac"><pre><span class="hljs-variable">@log_call</span>(CRITICAL=True) <span class="hljs-variable">@add_one</span> def <span class="hljs-built_in">pow</span>(x,y): <span class="hljs-built_in">return</span>(x**y)</pre></div><div id="7f64"><pre><span class="hljs-function"><span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-string">''</span>,call_count)</span></span> <span class="hljs-function"><span class="hljs-title">print</span><span class="hljs-params">(pow(<span class="hljs-number">2</span>,<span class="hljs-number">10</span>)</span></span>)</pre></div><p id="6336">The result is:</p><figure id="f99b"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*XKk1ZZiwrCUFa40wXs2P_A.png"><figcaption>Figure 7. The output of the pow function is wrapped with decorators log_call, wraps, and add_one. Image: a screenshot of the author’s terminal.</figcaption></figure><h1 id="ae47">6. Using Four Decorators: jit</h1><p id="9b5a">We use four decorators in this pattern. <a href="https://numba.readthedocs.io/en/stable/user/jit.html"><code>@</code>jit</a> is the acronym for <i>Just-in-Time</i> compilation into a C stub. Remember, <code>@wrap</code> is used inside the function <code>decorator</code>.</p><p id="720d">To show the effect of <a href="https://numba.readthedocs.io/en/stable/user/jit.html"><code>@</code>jit</a>, we don't use it at first.</p><div id="b9da"><pre><span class="hljs-variable">@log_call</span>(ERROR=True) <span class="hljs-variable">@add_one</span> def <span class="hljs-built_in">cum_one</span>(<span class="hljs-attribute">x</span>:int,<span class="hljs-attribute">y</span>:int) -> <span class="hljs-attribute">int</span>: total = <span class="hljs-number">1</span> for i in <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span>,y,<span class="hljs-number">1</span>): total += <span class="hljs-number">1</span> <span class="hljs-built_in">return</span>(total))</pre></div><div id="7717"><pre><span class="hljs-function"><span class="hljs-title">cum_one</span><span class="hljs-params">(<span class="hljs-number">2</span>,<span class="hljs-number">100</span>_000_000)</span></span></pre></div><figure id="7e1a"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*KCQIFcbRj7AA5HTYhTmeoA.png"><figcaption>Figure 7. The output of the cum_one function wrapped with decorators log_call, wraps, and add_one. Image: a screenshot of the author’s terminal</figcaption></figure><p id="1499">Without <code>@jit</code>, it required 7.09 seconds of wall clock time.</p><p id="ffe5"><b><i>Note</i></b><i>: <code>jit</code> is part of the <a href="http://numba.pydata.org/"><code>numba</code></a><code> </code>package.</i></p><div id="1145"><pre><span class="hljs-variable">@add_one</span> <span class="hljs-variable">@log_call</span>(ERROR=True) <span class="hljs-variable">@jit</span> def <span class="hljs-built_in">cum_one</span>(<span class="hljs-attribute">x</span>:int,<span class="hljs-attribute">y</span>:int) -> <span class="hljs-attribute">int</span>: total = <span class="hljs-number">1</span> for i in <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span>,y,<span class="hljs-number">1</span>): total += <span class="hljs-number">1</span> <span class="hljs-built_in">return</span>(total)</pre></div><figure id="022e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*NdPUWuJvLMEoQmwN5T1CvA.png"><figcaption>Figure 8. The output of the cum_one function wrapped with decorators add_one, log_call, wraps, and jit. Image: a screenshot of the author’s terminal</figcaption></figure><p id="2e2e">With <code>@jit</code>, it required 0.24 seconds of wall clock time. A speedup of approximately 30x.</p><p id="1697"><code>@jit</code> caches each compiled named function, resulting in more speedup in the second call. There is no <code>@jit</code> compilation overhead.</p><figure id="14fd"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*pJlsK9iZ4up2Aysj5YSWjQ.png"><figcaption>Figure 9. The output of the cum_one function demonstrating the effect of@it caching. Image: a screenshot of the author’s terminal.</figcaption></figure><p id="3023">The <code>@jit</code> result is 0.011 seconds of wall clock time. A speedup of approximately 640x.</p><h2 id="aaa8">The order of decorator invocation matters to @jit</h2><p id="1620">What happens when we invoke the <code>log_call</code> result and then <code>add_one</code>?</p><div id="0c82"><pre><span class="hljs-variable">@log_call</span>(ERROR=True) <span class="hljs-variable">@add_one</span> <span class="hljs-variable">@jit</span> def <span class="hljs-built_in">cum_one</span>(<span class="hljs-attribute">x</span>:int,<span class="hljs-attribute">y</span>:int) -> <span class="hljs-attribute">int</span>: total = <span class="hljs-number">1</span> for i in <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span>,y,<span class="hljs-number">1</span>): total += <span class="hljs-number">1</span> <span class="hljs-built_in">return</span>(total)</pre></div><div id="d19c"><pre><span class="hljs-function"><span class="hljs-title">cum_one</span><span class="hljs-params">(<span class="hljs-number">2</span>,<span class="hljs-number">100</span>_000_000)</span></span></pre></div><figure id="091d"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*Xwb2RCxiM9b53SIqWLsBNQ.png"><figcaption>Figure 10. The output of the cum_one function demonstrating the ordering of decorator invoking effect.Image: a screenshot of the author’s terminal.</figcaption></figure><p id="aab2">The log event of <code>log_call</code> comes before the log event of <code>add_one</code>.</p><p id="418c">Let's find out if we call <code>@jit</code> first.</p><div id="606f"><pre><span class="hljs-variable">@jit</span> <span class="hljs-variable">@log_call</span>(ERROR=True) <span class="hljs-variable">@add_one</span> def <span class="hljs-built_in">cum_one</span>(<span class="hljs-attribute">x</span>:int,<span class="hljs-attribute">y</span>:int) -> <span class="hljs-attribute">int</span>: total = <span class="hljs-number">1</span> for i in <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span>,y,<span class="hljs-number">1</span>): total += <span class="hljs-number">1</span> <span class="hljs-built_in">return</span>(total)</pre></div><div id="72ee"><pre><span class="hljs-function"><span class="hljs-title">cum_one</span><span class="hljs-params">(<span class="hljs-number">2</span>,<span class="hljs-number">100</span>_000_000)</span></span></pre></div><figure id="f3c1"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*VpKOmuhilu1VP_sxcD1svQ.png"><figcaption>Figure 11. The output of the cum_one function demonstrating @jit not the last decorator invoked effect. Image: a screenshot of the author’s terminal.</figcaption></figure><p id="0473">Ouch! It seems <code>@jit</code> needs to be invoked just before the function.</p><p id="c3b5">I stay away from philosophy. I am more of a <i>get-er-done</i> type of person. I will let you decide if this is a feature or bug of <code>@jit</code>.</p><h1 id="5532">Summary</h1><p id="bf51">In this article I discussed:</p><ol><li>Decorators with no arguments for logging.</li><li>Decorators with arguments for logging.</li><li>The value of <code>@wraps</code>.</li><li>Using three decorators.</li><li>The <code>@add_one</code> decorator.</li><li>Using four decorators.</li><li>What kind of speedup you can obtain with the <code>@jit</code> decorator.</li><li>The fact that the order of decorators is not transitive.</li></ol><p id="de27">The code in this article is given in and run in a <a href="https://github.com/bcottman/pyprobasic/blob/main/src/decorator-blog.ipynb">Jupyter notebook</a>.</p><p id="51a6">In the next article, I will show a package for conveniently logging and using parameters with a YAML file.</p><p id="b27e">Happy coding!</p></article></body>

6 Advanced Python Decorator Patterns

Code examples that explain the Python decorator through the development of templates

Figure 1. The decorator (lavender) wraps around a function (orange). Photo by Mister Starman on Unsplash.

Coding Experience With Computer Languages

In many ways, my coding experience resembles the sediment layers of ice on an archaeological dig.

The bottom — or oldest — layer is several different assemblers. The following layers are FORTRAN, Lisp (several variations), C, PL/1, SQL, C++, and Java in order of age of layer. The last layers, representing the last eight years, are a mixture of R, Python, Ruby, Scala, and Go.

Using macros in C, C++ are simple substitutions. Using macros in Java is considered a bad practice by many gurus. Are Lisp macros perhaps too powerful?

The most elegant macro, for me, is the Python decorator.

Note: Python would be my hands-down favorite language if it compiled into a static pseudo-code and had hands-free concurrency. To me, using PySpark, a cloud, or trampolining to another language to benefit from multi-core CPUs feels hacky.

I assume going forward that you are comfortable with beginner and advanced Python decorator usage.

If you need a refresher on Python decorator concepts, read the article below. It provides the best explanation of Python decorators that I have encountered:

I will walk through decorator patterns for logging, debugging, displaying function metadata, and enumeration in this article.

1. Basic Decorator Template: Logging

I will start with a general-purpose decorator I use for logging.

log_call is a basic Python decorator. It wraps the logging execution before and after the function call of add_one.

def log_call(fun):
    """
        Decorator @log_call wraps the funtion 
        with log events.
    """
    def wrapper(*args, **kwargs):
        #Pre:
        logger.info("before function: {}".format(fun.__name__))
        result = fun(*args, **kwargs)
        #post:
        logger.info("after function: {}, result:
                   {}".format(fun.__name__,result))
        return result
    return wrapper
@log_call
def add_one(x):
    x = x+1
    return(x)
y=0
y = add_one(y)
y
Figure 2. The output of add_one wrapped with decorator log_call. Image: a screenshot of the author's terminal.

The code is given in and was run in a Jupyter notebook.

The login_call decorator is our first pattern example. You can substitute any before or after boilerplate code or name you want in the login_call decorator pattern.

Decorator with arguments template (wrong)

If a decorator pattern had arguments, it would become even more powerful.

A naive Pythonic method to add arguments is to place *a, **kw after func in the call signature.

def log_call(func,*a, **kw):
    """
        Decorator @log_call with arguments wraps the funtion 
        with log events.
    """
    def wrapper(*args, **kwargs):
        #Pre
        result = func(*args, **kwargs)
        #post:
        logger.info("function: {}, result:{}".format(func.__name__,result))
        return result
    return wrapper
@log_call(1, ERROR=True)
def add_one(x):
    x = x+1
    return(x)
y=0
Figure 3. The output of the attempt to wrap add_one with decorator log_call with arguments. Image: a screenshot of the author’s terminal.

Adding *a, **kw after func in the call signature did not work.

The next section shows one correct way to create a decorator with arguments.

2. Decorator With Arguments: Logging (Correct)

A decorator with arguments needs @wrap and another function layer. I called this new function layer is arbitrarily named decorator.

def log_call(*a, **kw):
    """
        Decorator @log_call wraps the funtion 
        with log events.
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            #Pre:
            result = func(*args, **kwargs)
            #post:
            logger.info("function: {}, result:{}".format(func.__name__,result))
            return result
        return wrapper
    return decorator
@log_call(ERROR=True)
def add_one(x):
    x = x+1
    return(x)
y=0
y = add_one(y)
y
Figure 4. The output of add_one wrapped with decorator with arguments log_call. Image: a screenshot of the author’s terminal.

Next, we dig into @wrapand poke around a bit to find out why we need it.

3. @wrap Retains Function Metadata

It is usually not necessary to retain the function object instance metadata. However, we need the function object instance reference to correctly wrap the decorator.

The built-in Python function dir(function-symbol) details the function metadata. In the following example, we use dir(function-symbol) to create a decorator to detail the function's metadata.

def bad_dir(*a, **kw):
    """
        Decorator @bad_dir wraps a call of dir
        on the funtion inefficently.
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            #Pre:
            result = dir(func)
            #post:
            logger.info("function: {}, a:{},
                       kw()".format('bad_dir',a, kw))
            logger.info("function: {}, args:{}, kwargs()".format(func.__name__,args, kwargs))
            return result
        return wrapper
    return decorator
@bad_dir(1, ERROR=True)
def add_one(x):
    x = x+1
    return(x)
y=0
add_one(y)
Figure 5. The output of add_one wrapped with decorator bad_dir. Image: a screenshot of the author’s terminal.

The __name__ function object attribute is used by our log_calldecorator, but you can look through the list to find other useful function object attributes.

Note: @wrap is in the functools package.

4. Decorator With Arguments Template: Logging Uses Arguments

We need encapsulation code for a helper function that uses the log_call decorator's arguments.

What does the log_output function actually do in detail?

from typing import Dict, List, Any
def log_output(fun, result:Any, kw:Dict, check_state:List ) -> None:
    for key in kw:
        keyl = key.lower()
        if  keyl in check_state:
            if kw[key]:
                eval('logger.'+keyl)("function: {}, result:
                                   {}".format(fun.__name__,result))

We next create the log_call decorator to accept arguments using the new decorator pattern.

def log_call(*a, **kw):
    """
        Decorator @log_call wraps the funtion 
        with log events.
    """
    def decorator(fun):
        @wraps(fun)
        def wrapper(*args, **kwargs):
            #Pre:
            result = fun(*args, **kwargs)
            #post:
            check_state = ('debug', 'info', 'success',
                           'warning', 'error', 'critical')
            log_output(fun, result, kw, check_state)
            return result
        return wrapper
    return decorator
@log_call(ERROR=True)
def add_one(x):
    x = x+1
    return(x)
y=0
y = add_one(y)
y
Figure 6. The output of add_one wrapped with decorator with arguments log_call. Image: a screenshot of the author’s terminal.

The output shows us that we were successful in changing the logging state with the call @log_call(ERROR=True).

What would the output be with @log_call(ERROR=True, CRITICAL=True, FAIL=True)?

5. Using Two Decorators: add_one

We turn add_one into a decorator that increases the global call_count by one.

#GLOBAL 
call_count= 0
def add_one(fun):
    """
            Decorator pre-function call and post-function call of function func.
    """
    def wrapper(*args, **kwargs):
        global call_count
        #Pre action
        result = fun(*args, **kwargs)
        logger.info('call_count:{}'.format(call_count))
        call_count = call_count+1
        logger.info('Increase call_count:{}'.format(call_count))
        return result
    return wrapper

Actually, we use three decorators. @wraps counts as a decorator applied to the function pow.

@log_call(CRITICAL=True)
@add_one
def pow(x,y):
    return(x**y)
print('',call_count)
print(pow(2,10))

The result is:

Figure 7. The output of the pow function is wrapped with decorators log_call, wraps, and add_one. Image: a screenshot of the author’s terminal.

6. Using Four Decorators: jit

We use four decorators in this pattern. @jit is the acronym for Just-in-Time compilation into a C stub. Remember, @wrap is used inside the function decorator.

To show the effect of @jit, we don't use it at first.

@log_call(ERROR=True)
@add_one
def cum_one(x:int,y:int) -> int:
    total = 1 
    for i in range(2,y,1):
        total += 1
    return(total))
cum_one(2,100_000_000)
Figure 7. The output of the cum_one function wrapped with decorators log_call, wraps, and add_one. Image: a screenshot of the author’s terminal

Without @jit, it required 7.09 seconds of wall clock time.

Note: jit is part of the numba package.

@add_one
@log_call(ERROR=True)
@jit
def cum_one(x:int,y:int) -> int:
    total = 1 
    for i in range(2,y,1):
        total += 1
    return(total)
Figure 8. The output of the cum_one function wrapped with decorators add_one, log_call, wraps, and jit. Image: a screenshot of the author’s terminal

With @jit, it required 0.24 seconds of wall clock time. A speedup of approximately 30x.

@jit caches each compiled named function, resulting in more speedup in the second call. There is no @jit compilation overhead.

Figure 9. The output of the cum_one function demonstrating the effect of@it caching. Image: a screenshot of the author’s terminal.

The @jit result is 0.011 seconds of wall clock time. A speedup of approximately 640x.

The order of decorator invocation matters to @jit

What happens when we invoke the log_call result and then add_one?

@log_call(ERROR=True)
@add_one
@jit
def cum_one(x:int,y:int) -> int:
    total = 1 
    for i in range(2,y,1):
        total += 1
    return(total)
cum_one(2,100_000_000)
Figure 10. The output of the cum_one function demonstrating the ordering of decorator invoking effect.Image: a screenshot of the author’s terminal.

The log event of log_call comes before the log event of add_one.

Let's find out if we call @jit first.

@jit
@log_call(ERROR=True)
@add_one
def cum_one(x:int,y:int) -> int:
    total = 1 
    for i in range(2,y,1):
        total += 1
    return(total)
cum_one(2,100_000_000)
Figure 11. The output of the cum_one function demonstrating @jit not the last decorator invoked effect. Image: a screenshot of the author’s terminal.

Ouch! It seems @jit needs to be invoked just before the function.

I stay away from philosophy. I am more of a get-er-done type of person. I will let you decide if this is a feature or bug of @jit.

Summary

In this article I discussed:

  1. Decorators with no arguments for logging.
  2. Decorators with arguments for logging.
  3. The value of @wraps.
  4. Using three decorators.
  5. The @add_one decorator.
  6. Using four decorators.
  7. What kind of speedup you can obtain with the @jit decorator.
  8. The fact that the order of decorators is not transitive.

The code in this article is given in and run in a Jupyter notebook.

In the next article, I will show a package for conveniently logging and using parameters with a YAML file.

Happy coding!

Programming
Python
Software Development
Data Science
Coding
Recommended from ReadMedium
avatarAbhay Kumar
OOPs in Python

An easy guide

10 min read