Python Code Objects
Learning outcomes:
- What are code objects
- The
__code__
attribute - The
co_nlocals
code object attribute - The
co_varnames
code object attribute - The
co_names
code object attribute - The
co_freevars
andco_cellvars
code object attributes
Introduction
Python is a really flexible and powerful language capable of doing nearly every thing a developer can think of. Functions in Python are given special importance, as we've already seen in the last two chapters of this unit.
In this chapter, we shall see what are code objects for given functions and how they can be used to make certain decisions within functions.
Let's begin!
What are code objects?
Code objects are simply looks into the different aspects of a compiled function.
They expose various details about a function such as the number of positional arguments it must have; the number of its local variables; its free variables; and so on and so forth.
All these details regarding a function are collected when the bytecode for the Python program is generated. That's why these objects are called 'code' objects — they basically represent information given in the bytecode.
Every function in Python has a __code__
attribute that holds its code object.
This attribute contains a reference to the code object for the function. In the sections below, we'll see how to work with the __code__
attribute.
Inspecting the __code__
attribute
The __code__
attribute is one of the many attributes available on a Python function. As stated above, it represents the code object for the function.
Consider the code below:
def f(x):
return x
It simply defines a function f()
that returns the provided argument.
Let's inspect the __code__
attribute of this function f
:
We get back some representation of the code object, which is almost useless to us. What's a lot more useful is to inspect further attributes on this code object.
Based on the docs of Python, a code object has the following properties on it:
co_nlocals
— is the number of local variables used by the function (including arguments).co_argcount
— is the total number of positional arguments (including positional-only arguments and arguments with default values).co_varnames
— is a tuple containing the names of the local variables (starting with the argument names).co_names
— is a tuple containing the names used by the bytecode.co_cellvars
— is a tuple containing the names of local variables that are referenced by nested functions.co_freevars
— is a tuple containing the names of free variables; co_code is a string representing the sequence of bytecode instructions.co_posonlyargcount
— is the number of positional-only arguments (including arguments with default values).co_kwonlyargcount
— is the number of keyword-only arguments (including arguments with default values).co_firstlineno
— is the first line number of the function.co_lnotab
— is a string encoding the mapping from bytecode offsets to line numbers (for details see the source code of the interpreter).co_stacksize
— is the required stack size.co_code
— is a string representing the sequence of bytecode instructions.co_consts
— is a tuple containing the literals used by the bytecode.co_flags
— is an integer encoding a number of flags for the interpreter.
Let's take a closer look at the most common and useful of these properties.
co_nlocals
co_nlocals
holds the total number of local variables of a function. This obviously includes arguments as well, since arguments are also local variables.
Consider the function below:
def f(a, b, c):
d = 40
print(a, b, c, d)
print(f.__code__.co_nlocals)
As you can see, f.__code__.co_nlocals
returns 4
since f()
has a total of 4 local variables. One is a pure local variable (i.e d
) while the rest three are parameters (i.e a
, b
and c
).
f()
is not called, yet its information is accessible to us.co_argcount
As mentioned in Python's docs:
co_argcount
is the total number of positional arguments (including positional-only arguments and arguments with default values).As we know from the previous chapter, we can make 5 classifications of parameters of a function:
- Positional-only
- Positional or keyword
- Keyword-only
*args
**kwargs
The arg_count
attribute considers the first two of these.
Let's check it out:
def f(a, /, b, *, c, d):
pass
Here we have a function with four parameters: one is positional-only, one is normal while the rest two are keyword-only. There isn't really a need to execute anything in the function, likewise we leave it empty using the keyword pass
.
/
and *
over here, you can consider reading the previous Python Function Arguments chapter.Below we inspect its co_argcount
:
f.__code__.co_argcount
The co_argcount
here is 2
owing to the first two parameters of the function i.e a
and b
.
Why does co_argcount
use the word 'arg' ?
You might be confused as to why does the attribute co_argcount
have the word 'arg', which we know stands for 'argument'.
Technically speaking, a function can have a variable number of arguments so co_argcount
can only get a value at the time of calling the function. But we know that the attribute gets a value when the function is compiled, — not yet run.
Isn't this confusing?
The thing is that 'arg' here technically refers to the term 'formal arguments' of the function, which are also known as parameters. That's it!
co_varnames
The co_varnames
attribute contains a tuple holding all the names of local variables of the function starting from the names of parameters.
Consider the code below:
def f(a, b, c = 10):
x = 10
y = 20
Here the function has three parameters a
, b
and c
, and two pure local variables x
and y
.
As expected, the co_varnames
tuple begins at the parameters and then goes to the actual local variables:
f()
, len(f.__code__.co_varnames)
is equal to f.__code__.co_nlocals
.The order of parameters and local variables in the co_varnames
tuple, is the same as that of the parameters and local variables in the function's definition.
This is illustrated below:
def f(b, a, c = 10):
y = 10
x = 20
Compared to the previous code, here we've merely changed the order of parameters and the variables of the function.
Let's now see what we get in co_varnames
:
The order of elements in the tuple has changed according to the function's definition, just as was stated.
co_names
The co_names
attribute holds a tuple containing all global and built-in names used by a function.
As the simplest example, consider the code below:
a = 10
def foo(b, c, d):
def bar():
print(a, b, c, d)
pass
print(bar.__code__.co_names)
foo(1, 2, 3)
What interests us here is the output of line 7, that tells us the co_names
for the function bar()
. Following is the output:
The tuple holds two names, 'print'
and 'a'
, referring to a built-in function and a global variable, respectively.
See how the function bar()
refers to some variables of the enclosing function — b
, c
and d
— yet they don't get mentioned in co_names
. This is because they aren't global or built-in names.
co_freevars
The co_freevars
attribute returns a tuple containing the names of all free variables of a function.
So what exactly are free variables?
For instance, consider the code below:
def foo():
x = 10
def bar():
print(x)
return bar
The function bar()
refers to a variable x
that is neither a global variable, nor a local variable of the function. Rather, it belongs to the local scope of the enclosing function foo()
.
Hence, for bar()
, x
is a free variable.
The concept of free variables is crucial to understand before being able to fully comprehend the idea of closures in Python. Both of these concepts will be discussed in detail in the next chapter on Python Function Closures.
So if free variables trouble you right now, don't worry — we'll dig into every single bit of information regarding them in the next chapter.
co_cellvars
The co_cellvars
attribute, for a given function, returns a tuple containing the names of all cell variables of the function.
Once again, what are cell variables?
A cell variable is an idea related to that of a free variable. If some variable x
is a free variable for a function A()
then that same variable would be a cell variable for a function B()
.
Formally:
Consider the same previous code below:
def foo():
x = 10
def bar():
print(x)
return bar
As we know from the previous section, x
here is a free variable for bar()
since it's not local to bar()
, neither a global variable.
But for foo()
, this same variable x
is a cell variable. This is because, it's local to foo()
and used by an inner function — bar()
in this case.
As before, if you are having a hard time understanding what exactly is a cell variable, or that why is it even called a cell variable, don't worry — the next chapter will address all your questions.