Function Introspection in Python

Function Introspection in Python

Functions in Python are objects, so we can use object tools. For example, we can inspect their attributes and methods.

def korek(a,b):

“””Adding two integers.”””

return a+b

Let’s call it:

>>> korek(2,3)
5
>>>

It works! So now let’s see its attributes and methods:

>>> dir(korek)
[‘__annotations__’, ‘__call__’, ‘__class__’, ‘__closure__’, ‘__code__’, ‘__def
lts__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__
 ‘__ge__’, ‘__get__’, ‘__getattribute__’, ‘__globals__’, ‘__gt__’, ‘__hash__’,
__init__’, ‘__kwdefaults__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__name__’, ‘__
__’, ‘__new__’, ‘__qualname__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__
tattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’]
>>>

A lot of stuff! We have used dir() that is very useful built-in function.

All these names has two underscores at the beginning and at the end, so they are special attributes and methods. Objects possess attributes and methods. We won’t be using most of them because they are used only in Object-Oriented Programming. For our functions in Structured Programing, we will be using only some of them.

We can use them in this way:

object.method

object,attribute

So let’s try do something:

>>> korek.__name__
‘korek’

We have the name of our function.
>>> korek.__doc__
‘Adding two integers.’

We have the docstring of our function.
>>> korek.__code__
<code object korek at 0x00B5BA20, file “<stdin>”, line 1>
>>>

We have the place in memory where the object exists.

But we can go deeper:

object.method.method/attribute

object,attribute.method/attribute

For example, let’s inspect korek.__code__:

>>> dir(korek.__code__)
[‘__class__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__
__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__le__’, ‘__lt__’,
__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’,
_sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘co_argcount’, ‘co_cellvars’, ‘co_c
e’, ‘co_consts’, ‘co_filename’, ‘co_firstlineno’, ‘co_flags’, ‘co_freevars’, ‘
_kwonlyargcount’, ‘co_lnotab’, ‘co_name’, ‘co_names’, ‘co_nlocals’, ‘co_stacks
e’, ‘co_varnames’]
>>>

We see the same attributes and methods like in the case of dir(korek) because they are default attributes/methods for almost all objects. However, there are others that start with co_. Let’s use them:

>>> korek.__code__.co_varnames
(‘a’, ‘b’)
>>>

We’ve got our local variables in the function.

>>> korek.__code__.co_names
()
>>>

Empty! There are no names of globals and attributes in our function.
>>> korek.__code__.co_filename
‘<stdin>’
>>>

Yes, the file is stdin (standard input) that is the name of file in which this code object was created.

Let’s look at other attributes/methods for code:

co_argcount number of arguments (not including * or ** args)

co_code string of raw compiled bytecode

co_consts tuple of constants used in the bytecode

co_filename name of file in which this code object was created

co_firstlineno number of first line in Python source code

co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg

co_lnotab encoded mapping of line numbers to bytecode indices

co_name name with which this code object was defined

co_names tuple of names of local variables

co_nlocals number of local variables

co_stacksize virtual machine stack space required

co_varnames tuple of names of arguments and local variables

We can check other function attribute:

>>> dir(korek.__name__)
[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’, ‘_
_eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’, ‘__getnewargs
__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__iter__’, ‘__le__’, ‘__len__’, ‘__lt__’
, ‘__mod__’, ‘__mul__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__r
epr__’, ‘__rmod__’, ‘__rmul__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subcl
asshook__’, ‘capitalize’, ‘casefold’, ‘center’, ‘count’, ‘encode’, ‘endswith’, ‘
expandtabs’, ‘find’, ‘format’, ‘format_map’, ‘index’, ‘isalnum’, ‘isalpha’, ‘isd
ecimal’, ‘isdigit’, ‘isidentifier’, ‘islower’, ‘isnumeric’, ‘isprintable’, ‘issp
ace’, ‘istitle’, ‘isupper’, ‘join’, ‘ljust’, ‘lower’, ‘lstrip’, ‘maketrans’, ‘pa
rtition’, ‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rpartition’, ‘rsplit’, ‘rstrip
‘, ‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’, ‘translate
‘, ‘upper’, ‘zfill’]
>>>

Something strange? Yes, let’s look at that:

>>> dir(str)
[‘__add__’, ‘__class__’, ‘__contains__’, ‘__delattr__’, ‘__dir__’, ‘__doc__’, ‘_
_eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__getitem__’, ‘__getnewargs
__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__iter__’, ‘__le__’, ‘__len__’, ‘__lt__’
, ‘__mod__’, ‘__mul__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__r
epr__’, ‘__rmod__’, ‘__rmul__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subcl
asshook__’, ‘capitalize’, ‘casefold’, ‘center’, ‘count’, ‘encode’, ‘endswith’, ‘
expandtabs’, ‘find’, ‘format’, ‘format_map’, ‘index’, ‘isalnum’, ‘isalpha’, ‘isd
ecimal’, ‘isdigit’, ‘isidentifier’, ‘islower’, ‘isnumeric’, ‘isprintable’, ‘issp
ace’, ‘istitle’, ‘isupper’, ‘join’, ‘ljust’, ‘lower’, ‘lstrip’, ‘maketrans’, ‘pa
rtition’, ‘replace’, ‘rfind’, ‘rindex’, ‘rjust’, ‘rpartition’, ‘rsplit’, ‘rstrip
‘, ‘split’, ‘splitlines’, ‘startswith’, ‘strip’, ‘swapcase’, ‘title’, ‘translate
‘, ‘upper’, ‘zfill’]
>>>

It’s the same output. It’s simple to explain — the name of our function is str type because it’s a string. It’s a proof that all objects in Python are related because they inherit from one base class that is object and the type called type.

All these attributes/methods are in the function object by default. But we can add our own attributes. Let’s do that:

def korek():

“””Do nothing.”””

pass

We have the function, and we can add three attributes:

>>> korek.a = 2
>>> korek.b = 4
>>> korek.c=(korek.a+korek.b)

Let’s check if they exist in our function:

>>> korek.a
2
>>> korek.b
4
>>> korek.c
6
>>>

Yes, they are true attributes. They are constant, but we can change them if we want:

>>> korek.a = 3
>>> korek.b = 5
>>> korek.c = (korek.a+korek.b)
>>> korek.a
3
>>> korek.b
5
>>> korek.c
8
>>>

We can check if our three attributes appear among default attributes:

>>> dir(korek)
[‘__annotations__’, ‘__call__’, ‘__class__’, ‘__closure__’, ‘__code__’
lts__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__
 ‘__ge__’, ‘__get__’, ‘__getattribute__’, ‘__globals__’, ‘__gt__’, ‘__
__init__’, ‘__kwdefaults__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__name
__’, ‘__new__’, ‘__qualname__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr
tattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘a’, ‘b’, ‘c’]
>>>

Everything is OK — we can see a, b, c.

We can use such attributes to attach state information to our function objects directly. Attributes are accessible anywhere in the function code. They are as though static locals that are local to our function, but at the same time they exist after a function having finished.

Leave a comment