How to Create Decorators in Python That You Can Actually Use


Consider this conversation between Bob and Job:

  • Bob: ‘Jon, why did not you come to the lesson yesterday?’
  • Jon: ‘I had a flu…’

Not the best of stories but when Bob asks the reason for Jon’s absence in yesterday’s class, we know he is referring to the Jon standing next to him not some random Jon in another country. As humans, it is not difficult to notice this but programming languages use something called scope to tell which name we are referring to in our programs.

In Python, names can be variables, function names, module names, etc.

Consider these two variables:

>>> a = 24
>>> b = 42
>>> print(a)

Here, print had no trouble to tell that we are referring to the a we just defined. Now consider this:

>>> def foo():
... a = 100
... print(a)

What do you think will happen if we run foo? Will it print 24 or 100?

>>> foo()

How did Python differentiate between the a we defined in the beginning or in the function? This is where scope gets interesting, because we are introducing layers of scope:

The above image shows the scope for this little script:

The global scope is the overall scope of your script/program. Variables, functions, etc. with the same indentation level as a and b defined in the beginning will be in the global scope. For example, foo function is in the global scope but its variable a is in the scope that is local to foo.

In one global scope, there can be many local scopes. For example, each temporary variables in for loops and list comprehensions, return values of context managers will be local inside their code block and cannot be accessed from the global scope.

So, a rule of thumb is that Python interpreter will not be able to access a name defined in a smaller scope than the current one.

There is also a bigger level of scope outside global:

The built-in scope contains all the modules and packages you installed with Python, pip or conda.

Now, let’s explore another case. In our foo function, we want to modify the value of global a. We want it to be a string but if we write a = 'some text' inside the foo, Python will just create a new variable without modifying the global a.

Python provides us with a keyword that lets us specify we are referring to names in the global scope:

Writing global <name> will let us modify the values of names in the global scope.

BTW, bad news, I left out one level of scope in the above image. Between global and local, there is one level we did not cover:

nonlocal scope comes into play when we have, for example, nested functions:

In nested function outer, we first create a variable called my_var and assign it to the string Python. Then we decide to create a new inner function and want to assign my_var a new value, Data Science and print it. But if we run it, we see that my_var is still assigned to ‘Python’. We cannot use global keyword since my_var is not in the global scope.

For such cases, you can use nonlocal keyword which gives access to all the names in the scope of the outer function (nonlocal) but not the global:

In conclusion, scope tells the Python interpreter where to look for names in our program. There can be four levels of scope in a single script/program:

  • Built-in: all the package names installed with Python, pip and conda
  • Global: general scope, all names that have no indentation in the script
  • Local: contains local variables in each code block such as functions, loops, list comprehensions, etc.
  • Nonlocal: an extra level of scope between global and local in the case of nested functions

Read More …


Write a comment