python-course.eu

12. Argument Count

By Bernd Klein. Last modified: 08 Mar 2024.

Introduction

Arguments and Parameters in a Wordcloud

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

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here

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

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Upcoming online Courses

Enrol here