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.
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 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
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 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() has a total of 4 local variables. One is a pure local variable (i.e
d) while the rest three are parameters (i.e
f()is not called, yet its information is accessible to us.
As mentioned in Python's docs:
co_argcountis 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 or keyword
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
*over here, you can consider reading the previous Python Function Arguments chapter.
Below we inspect its
co_argcount here is
2 owing to the first two parameters of the function i.e
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 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
c, and two pure local variables
As expected, the
co_varnames tuple begins at the parameters and then goes to the actual local variables:
len(f.__code__.co_varnames)is equal to
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
The order of elements in the tuple has changed according to the function's definition, just as was stated.
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,
'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 —
d — yet they don't get mentioned in
co_names. This is because they aren't global or built-in names.
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
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
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 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
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.
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.