12. Argument Count
By Bernd Klein. Last modified: 08 Mar 2024.
Introduction
Have you ever wondered, "How can I determine the number of arguments in a Python function?" If you're a newcomer to Python, it's unlikely that such a query would even cross your mind. You might simply ask, "What's the point?" or "Why bother". The solution is straightforward: you can either analyze the code or refer to the documentation.
As your proficiency in Python increases, and when you delve into advanced topics like decorators, composing of functions or currying, things look different. You begin crafting code for functions that are unknown at the time of writing So there is no direct way to look at "the function".
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
An Example
As an example, you can examine the following code for a decorator that tracks how many times a function has been invoked, regardless of the function's argument count or the specific arguments passed to the 'wrapper' function.
def call_counter(func):
def wrapper(*args, **kwargs):
helper.calls += 1
return func(*args, **kwargs)
helper.calls = 0
return helper
Let's define the following functions:
def greet(name):
return f"Hello, {name}!"
def calculate_rectangle_area(length, width):
return length * width
def calculate_surface_area(length, width, height):
return 2 * (length * width + width * height + height * length)
def display_info(name, age, *args, **kwargs):
print(f"Name: {name}")
print(f"Age: {age}")
print("Additional Info:")
for info in args:
print(info)
print("Keyword Arguments:")
for key, value in kwargs.items():
print(f"{key}: {value}")
Now we decorate these functions with the following decorator:
def describe_parameters(func):
def wrapper(*args, **kwargs):
print(f'{args=}, {kwargs=}')
print(f'{len(args)=}, {len(kwargs)=}')
return func(*args, **kwargs)
return wrapper
greet = describe_parameters(greet)
calculate_rectangle_area = describe_parameters(calculate_rectangle_area)
calculate_surface_area = describe_parameters(calculate_surface_area)
display_info = describe_parameters(display_info)
print(greet('Mike'))
OUTPUT:
args=('Mike',), kwargs={} len(args)=1, len(kwargs)=0 Hello, Mike!
calculate_surface_area(5, 6, 7)
OUTPUT:
args=(5, 6, 7), kwargs={} len(args)=3, len(kwargs)=0 214
display_info('Emily', 42, 'Berlin', 'Learning Python')
OUTPUT:
args=('Emily', 42, 'Berlin', 'Learning Python'), kwargs={} len(args)=4, len(kwargs)=0 Name: Emily Age: 42 Additional Info: Berlin Learning Python Keyword Arguments:
Indeed, based solely on the way these functions are called within the wrapper function, we cannot definitively determine their actual function signatures. The functions could have been defined with individual and different parameter names or with the use of *args
and **kwargs
, making the exact function signatures unclear.
For example, calculate_surface_area
could be defined as def calculate_surface_area(*dimensions)
to accept a variable number of dimensions, and display_info
might be defined as def display_info(name, age, city, interest)
with specific named parameters.
The way the functions are used within the wrapper function doesn't provide enough information to determine the exact function signatures; the details of these signatures would need to be examined within their respective definitions in the code.
The __code__
Attribute
The __code__
attribute of a Python function object contains a wealth of additional information on the arguments. Don't be alarmed by the warning in the help function that states, "Not for the faint of heart." Yet, the help lists only the attributes without explaining them.
One of these is the co_argcount
sub-attribute, which returns the number of positional arguments (including arguments with default values).
Let's demonstrate this with the following functions:
def calculate_surface_area(length, width, height):
return 2 * (length * width + width * height + height * length)
def calculate_surface_area_defaults(length, width=14, height=42):
return 2 * (length * width + width * height + height * length)
def display_info(name, age, *args, **kwargs):
print(f"Name: {name}")
print(f"Age: {age}")
print("Additional Info:")
for info in args:
print(info)
print("Keyword Arguments:")
for key, value in kwargs.items():
print(f"{key}: {value}")
for func in [calculate_surface_area, calculate_surface_area_defaults, display_info]:
print(f'{func.__name__}.__code__.co_argcount={func.__code__.co_argcount}')
OUTPUT:
calculate_surface_area.__code__.co_argcount=3 calculate_surface_area_defaults.__code__.co_argcount=3 display_info.__code__.co_argcount=2
We will use now the function all-together-now
from our chapter Parameters and Arguments
We added also a nested function and a local variable:
def all_together_now(a, /, b, c=11, d=12, *args, kw_only1=42, kw_only2=84, **kwargs):
local1 = 'whatever'
def nested_func(x):
return x + 42
return f'{a=}, {b=}, {c=}, {d=}, {args=}, {kw_only1=}, {kw_only2=}, {kwargs=}'
all_together_now(2, name='Guido', age=42, kw_only1=99, b=1001)
OUTPUT:
"a=2, b=1001, c=11, d=12, args=(), kw_only1=99, kw_only2=84, kwargs={'name': 'Guido', 'age': 42}"
Let's have a look at the number of positional arguments (including positional-only arguments and arguments with default values):
all_together_now.__code__.co_argcount
OUTPUT:
4
The number 4
stands for the parameters a
, b
, c
and d
Let's have a look at the number of the keyword-only parameters:
all_together_now.__code__.co_kwonlyargcount
OUTPUT:
2
help(all_together_now.__code__)
OUTPUT:
Help on code object: class code(object) | code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, flags, codestring, constants, names, varnames, filename, name, qualname, firstlineno, linetable, exceptiontable, freevars=(), cellvars=(), /) | | Create a code object. Not for the faint of heart. | | Methods defined here: | | __eq__(self, value, /) | Return self==value. | | __ge__(self, value, /) | Return self>=value. | | __getattribute__(self, name, /) | Return getattr(self, name). | | __gt__(self, value, /) | Return self>value. | | __hash__(self, /) | Return hash(self). | | __le__(self, value, /) | Return self<=value. | | __lt__(self, value, /) | Return self<value. | | __ne__(self, value, /) | Return self!=value. | | __repr__(self, /) | Return repr(self). | | __sizeof__(...) | Size of object in memory, in bytes. | | co_lines(...) | | co_positions(...) | | replace(self, /, *, co_argcount=-1, co_posonlyargcount=-1, co_kwonlyargcount=-1, co_nlocals=-1, co_stacksize=-1, co_flags=-1, co_firstlineno=-1, co_code=None, co_consts=None, co_names=None, co_varnames=None, co_freevars=None, co_cellvars=None, co_filename=None, co_name=None, co_qualname=None, co_linetable=None, co_exceptiontable=None) | Return a copy of the code object with new values for the specified fields. | | ---------------------------------------------------------------------- | Static methods defined here: | | __new__(*args, **kwargs) from builtins.type | Create and return a new object. See help(type) for accurate signature. | | ---------------------------------------------------------------------- | Data descriptors defined here: | | co_argcount | | co_cellvars | | co_code | | co_consts | | co_exceptiontable | | co_filename | | co_firstlineno | | co_flags | | co_freevars | | co_kwonlyargcount | | co_linetable | | co_lnotab | | co_name | | co_names | | co_nlocals | | co_posonlyargcount | | co_qualname | | co_stacksize | | co_varnames
The number 2
stands for the parameters kw_only1
and kw_only2
It's also possible to get the number of positional only parameters:
all_together_now.__code__.co_posonlyargcount
OUTPUT:
1
The only positional only variable, i.e. variables in front of the '/', is a!
What about the count of local variables? How many local variables do you think the functions has? A lot of you are wrong, I am sure! The parameters are counted as local variables as well and the attribute co_nlocals
provides us with a count:
all_together_now.__code__.co_nlocals
OUTPUT:
10
You counted to 9? Right? If so, you forgot to see that the function nested_func
is local and it's name is considered to be a local variable.
Live Python training
Enjoying this page? We offer live Python training courses covering the content of this site.
Upcoming online Courses