Functions in Python  - Part 3

Functions in Python - Part 3

Understanding Python functions

In this comprehensive article, we will delve into the intricacies of how *args and **kwargs operate, and explore the significant role they play in enhancing the flexibility and functionality of functions within the Python programming language. By understanding these powerful features, you will be better equipped to create more versatile and dynamic code, allowing you to handle a wide range of input arguments and keyword arguments with ease.

*args

*args is a special syntax in Python that allows a function to accept a variable number of positional arguments. The term "args" is short for "arguments." When you use *args as a parameter in a function definition, it allows the function to accept any number of positional arguments, and those arguments are collected into a tuple within the function.

Here's a basic example of how *args works:

def my_function(*args):
    for arg in args:
        print(arg)

my_function(1, 2, 3, "hello")

In this example, my_function accepts any number of positional arguments, and it prints each argument one by one. When you call my_function(1, 2, 3, "hello"), it will print:

1
2
3
hello
๐Ÿ’ก
The parameter name is arbitrary โ€“ you can make it whatever you want. It is customary (but not required) to name it args.

Key points about *args:

  1. The *args parameter should be the last parameter in the function's parameter list. Any regular positional parameters must come before it.

  2. You can name the *args parameter differently, but *args is a common convention and makes the code more readable.

  3. You can use any number of positional arguments when calling a function that uses *args. They will be collected into a tuple within the function.

  4. You can combine *args with other regular parameters. For example:

     def example_function(arg1, arg2, *args):
         # arg1 and arg2 are regular parameters
         # *args collects any additional positional arguments
         pass
    
  5. You can use *args in functions to make them more flexible and capable of handling a variable number of arguments without explicitly specifying each argument in the function's signature.

Here's a more practical example where *args is used to calculate the sum of a variable number of numbers:

def calculate_sum(*args):
    return sum(args)

result = calculate_sum(1, 2, 3, 4, 5)
print(result)  # Output: 15

In this case, calculate_sum can accept any number of arguments, and it calculates their sum.

def func1(a, b, c):
    # code

l = [10, 20, 30]

func1(*l)

In the provided example, it is not possible to pass the list 'l' directly as an argument to the function 'func1'. Instead, we must first unpack the elements of the list and then pass them individually to the function. This is achieved by using the asterisk (*) operator, which allows us to unpack the elements of the list 'l' and pass them as separate arguments to the function 'func1'.

Keyword Arguments

Recall that positional parameters can, optionally be passed as named (keyword) arguments. We can make keyword arguments mandatory. To do so, we create parameters after the positional parameters have been exhausted.

def func(a, b, *args, d):
    #code

In this case, *args effectively exhausts all positional arguments and d must be passed as a keyword (named) argument.

func(1, 2, 'x', 'y', d = 100) โ†’ a = 1, b = 2, args = ('x', 'y'), d = 100

func(1, 2, d = 100) โ†’ a = 1, b = 2, args = (), d = 100

func(1, 2) #This is wrong d was not a keyword argument

We can even omit any mandatory positional arguments:

def func(*args, d):
    #code

func(1, 2, 3, d=100) โ†’ args = (1, 2, 3), d = 100

func(d=100) โ†’ args = (), d = 100

In fact we can force no positional arguments at all:

def func(*, d):
    #code

func(1, 2, 3, d=100) โ†’ Exception

func(d=100) โ†’ d = 100

* indicates the "end" of positional arguments

Putting it together

def func(a, b=1, *args, d, e=True):
    # code

def func(a, b=1, *, d, e=True):
    # code

a: mandatory positional argument (may be specified using a named argument)

b: optional positional argument (may be specified positionally, as a named argument, or not at all), defaults to 1

args: catch-all for any (optional) additional positional arguments

d: mandatory keyword argument

e: optional keyword argument, defaults to True

**kwargs

**kwargs is another special syntax in Python, similar to *args, but it is used to accept a variable number of keyword arguments in a function. The term "kwargs" is short for "keyword arguments." When you use **kwargs as a parameter in a function definition, it allows the function to accept any number of keyword arguments, and those arguments are collected into a dictionary within the function.

Here's an example of how **kwargs works:

def my_function(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

my_function(name="Alice", age=25, city="New York")

In this example, my_function accepts any number of keyword arguments, and it prints each argument as a key-value pair. When you call my_function(name="Alice", age=25, city="New York"), it will print:

name: Alice
age: 25
city: New York

Key points about **kwargs:

  1. The **kwargs parameter should also be the last parameter in the function's parameter list, just like *args. Any regular positional and keyword parameters must come before it.

  2. You can name the **kwargs parameter differently, but **kwargs is a common convention and makes the code more readable.

  3. You can use any number of keyword arguments when calling a function that uses **kwargs. They will be collected into a dictionary within the function, with the argument names as keys and the corresponding values.

  4. You can use **kwargs in functions to make them more flexible and capable of handling a variable number of keyword arguments without explicitly specifying each argument in the function's signature.

Here's an example of using **kwargs to create a function that builds a dictionary with user-defined key-value pairs:

def build_dictionary(**kwargs):
    return kwargs

result = build_dictionary(name="John", age=30, city="Los Angeles")
print(result)  # Output: {'name': 'John', 'age': 30, 'city': 'Los Angeles'}

In this case, build_dictionary accepts any number of keyword arguments, and it returns a dictionary containing those arguments.

Putting it all together

positional argumentskeyword-only arguments
*args is used to scoop up a variable amount of remaining positional arguments. The parameter name args is arbitrary โ€“ * is the real performer here**kwargs is used to scoop up a variable amount of remaining keyword arguments. The parameter name kwargs is arbitrary โ€“ ** is the real performer here
tupledictionary
may have default valuesmay have default values

Typical Use Case: Python's print() function

*objects arbitrary number of positional arguments after that are keyword-only arguments they all have default values, so they are all optional

In conclusion, understanding and utilizing args and *kwargs in Python functions significantly enhances their flexibility and functionality. By employing these powerful features, you can create versatile and dynamic code that can handle a wide range of input arguments and keyword arguments with ease, making your programs more adaptable and efficient.

Practice Use case:

Data Validation and Parsing: Write a function that validates and parses user input, such as a date or email address, and returns it in a structured format.

File Manipulation: Write functions to perform various file operations, such as copying, moving, or deleting files, and handling exceptions gracefully.

Password Strength Checker: Develop a function that checks the strength of a password based on criteria like length, character types, and uniqueness.

Mathematical Calculations: Create functions for solving complex mathematical problems or equations, such as finding prime factors or solving quadratic equations.

Did you find this article valuable?

Support TechWhisperer by becoming a sponsor. Any amount is appreciated!

ย