Python Basics: Functions

Functions are modular blocks of code designed to perform specific tasks. They encapsulate a set of instructions under a meaningful name, making the code more readable, reusable, and manageable.

Python Basics: Functions

For more posts about Python, follow the link below.👇👇👇

Exploring Python
Python is frequently utilized in creating websites and software, as well as for automating tasks, analyzing data, and visualizing information.

Previously we learned to control program flow using loops and conditional statements. If you haven't gone through it yet, follow the link below. 👇👇👇

Python Basics: Loops and conditional statements
Loops and conditional statements are fundamental constructs that empower programmers to create dynamic and responsive programs. These tools enable the execution of specific code blocks based on certain conditions, as well as the repetitive execution of code to process data efficiently.

Let's get back for a moment to the sample PyCharm Python script that started our journey. It is stripped from comments, leaving only pure interpretable code.

def print_hi(name):
    print(f'Hi, {name}')

if __name__ == '__main__':
    print_hi('PyCharm')

If we take a look carefully, we can notice that we have been using functions since day one! print_hi(...) is a function defined by us and print(...) is Python's built-in function. Conversion between types is actually a function call. Since the functions are commonly present, it's worth taking some time to understand them.

What functions are?

Functions are modular blocks of code designed to perform specific tasks. They encapsulate a set of instructions under a meaningful name, making the code more readable, reusable, and manageable. They promote code organization and reduce redundancy, allowing developers to efficiently build and maintain complex programs. By utilizing functions, programs can be broken down into smaller units, enhancing code structure and facilitating collaboration.

Here’s the basic syntax of defining a function in Python.

def function_name(parameter):
    # Function body
    return result # Optionally, a function can return a value

Let's break it down into components:

  • def This keyword signals the start of a function definition.
  • function_name This is the name given to the function. Choose a descriptive name that reflects the purpose of the function.
  • parameter This is the input value that the function accepts. It goes inside the parentheses and can be zero or more. If the function doesn’t need any parameters, the parentheses remain empty.
  • : The colon marks the beginning of the function body.
  • Function body This is where we write the code that the function will execute when called. It performs the intended task using the provided parameters.
  • return If the function needs to produce an output, the return statement followed by an expression specifies the value to be returned. If the function doesn’t return anything, we can omit the return statement or use it without an expression to return from a function earlier.

To execute a function, we simply call it by its name and provide optional parameters, and store the return value if any. Let's see an example.

returnedValue = function_name(aParameter)

Now we are ready to write the simplest function.

def print_hello():
    print('Hello world!')

print_hello()

print_hello(...) function doesn't take any parameters and doesn't provide any returned value. In its body, it calls Python's built-in print(...) function and passes "Hello world!" string.

❯ python3 main.py
Hello world!

"Hello world!" message is printed on the terminal!

Returning outcome from a function

Usually, functions are useful to perform some operation and provide a result to the caller. To make things a bit more complicated, let's define a function that computes a sum of two numbers.

def add_numbers(a, b):
    sum_result = a + b
    return sum_result

result = add_numbers(5, 3)
print(result)

add_numbers(...) function takes two arguments, a and b, it sums them up and stores the result in sum_result variable. Then the variable is returned providing the outcome to the caller.

As a function can take multiple parameters, it also can produce multiple outcomes.

def add_and_subtract_numbers(a, b):
    sum_result = a + b
    diff_result = a - b
    return sum_result, diff_result

sum_result, diff_result = add_and_subtract_numbers(10, 5)
print("Sum:", sum_result)
print("Difference:", diff_result)

In this case, the add_and_subtract_numbers(...) function returns two values, which are unpacked into the sum_result and diff_result variables.

💡
Unpacking in Python refers to the process of extracting elements from data structures like tuples, lists, or dictionaries, and assigning them to individual variables. This allows us to easily access the individual elements within a collection without needing to access them using indexing or iteration.

Sometimes functions return more than one value, but if we are not interested in all of them, we may want to discard a few.

def add_and_subtract_numbers(a, b):
    sum_result = a + b
    diff_result = a - b
    return sum_result, diff_result

_, diff_result = add_and_subtract_numbers(10, 5)
print("Difference:", diff_result)

The above example shows that we are not interested in sum_result. To ignore that returned value, we can use underscore.

💡
Discarding a variable using an underscore (_) is a common practice in Python when we receive a value that we don’t intend to use. By assigning the value to an underscore, we indicate that the value is intentionally being ignored or discarded.

Default and named parameters

Default parameters, also known as default arguments or default parameter values, are values assigned to function parameters in advance. These default values are used when a caller of the function does not provide a value for the corresponding parameter. This feature is particularly useful when we want a parameter to have a default behavior but still allow callers to customize it if needed.

In Python, we can define default parameters when declaring a function by assigning a value to the parameter in the function’s parameter list, i.e. parameter_name=value. When the function is called, if an argument is not provided for that parameter, the default value will be used.

Let's customize our print_hi(...) function.

def print_hi(name, message='Hi'):
    print(f'{message}, {name}')

if __name__ == '__main__':
    print_hi('PyCharm')

The function now takes two arguments, but the second one is defaulted. We can still call the function providing a single parameter.

❯ python3 main.py
Hi, PyCharm

Now we can provide the second parameter, overriding the default one.

def print_hi(name, message='Hi'):
    print(f'{message}, {name}')

if __name__ == '__main__':
    print_hi('PyCharm', 'Hello')

We are providing "Hello" as the second parameter.

❯ python3 main.py
Hello, PyCharm

The output has changed, and now the "Hello, PyCharm" message is printed on the terminal.

When a function has several parameters, named parameters, also known as keyword arguments are especially useful if some of them have default values. It is a feature in programming languages that allow us to pass arguments to a function using the names of the parameters rather than relying on their positions. This provides more clarity and flexibility when calling functions, as we can explicitly specify which parameter each argument corresponds to, regardless of the order.

In Python, you can use named parameters when calling functions by using the syntax parameter_name=value. This allows us to pass arguments to the function in any order, making the code more readable and reducing the chances of making mistakes due to argument ordering.

def print_hi(name, message='Hi'):
    print(f'{message}, {name}')

if __name__ == '__main__':
    print_hi(message='Hello', name='PyCharm')

In this example, we’ve called the print_hi(...) function with named parameters, explicitly specifying the values for the message and name parameters. The order in which the arguments are passed doesn’t matter because the names are used to match the values to the correct parameters.

Named parameters are especially useful when a function has several parameters, some of which have default values. We can choose to only provide values for the parameters we want to customize, and the rest will use their default values.

Inner functions

Inner functions, also known as nested functions, are functions defined inside another function in Python. These inner functions are encapsulated within the scope of the outer function and can only be accessed and used within that scope. This concept of nesting functions allows for more modular and organized code, as well as the ability to create helper functions that are closely related to the main function.

def print_hi_outer(message):
    def print_hi_inner(name):
    	print(f'{message}, {name}')
    return print_hi_inner

if __name__ == '__main__':
    closure = print_hi_outer('Hi')
    closure('Robert')
    closure('Paul')
    closure = print_hi_outer('Hello')
    closure('Robert')
    closure('Paul')

In this example, the print_hi_inner(...) function is defined within the scope of the print_hi_outer(...) function. The inner function has access to the parameter message of the outer function, even after the outer function has completed execution. This behavior is known as a “closure.” The print_hi_outer(...) function returns the print_hi_inner(...) function, creating a closure with the value of message captured.

❯ python3 main.py
Hi, Robert
Hi, Paul
Hello, Robert
Hello, Paul

Inner functions are often used to encapsulate logic that’s only relevant to a specific context or to create custom behavior for different instances of a function. They help keep code organized, modular, and easier to understand by breaking down complex tasks into smaller, manageable pieces.

Global, local, and nonlocal

In Python, the visibility of a variable is drawn by the scope where it's been defined. In particular, we can differentiate three types of variables' scopes: local, nonlocal, and global.

Local variables are variables that are defined within a function and are only accessible within that function’s scope. They cannot be accessed from outside the function.

def my_function():
    x = 10
    print(x)

my_function()
print(x)

Here we have my_function(...) that defines the x variable.

❯ python3 main.py
10
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    print(x)
NameError: name 'x' is not defined

When executing the script we get the NameError exception as the variable is not visible in this scope.

Global variables are variables defined outside of any function, at the top level of the script or module. They can be accessed from any part of the code, including within functions.

y = 20

def another_function():
    print(y)

another_function()

The another_function(...) uses the y variable that is defined outside of its body.

❯ python3 main.py
20

The script prints 20 and exits normally. Now we can slightly modify it, so before y is printed, it is increased by one.

y = 20

def another_function():
    y += 1
    print(y)

another_function()

When we run the script, we encounter the following issue.

❯ python3 main.py
Traceback (most recent call last):
  File "main.py", line 7, in <module>
    another_function()
  File "main.py", line 4, in another_function
    y += 1
UnboundLocalError: local variable 'y' referenced before assignment

When executing the script we get the UnboundLocalError exception as the variable is unknown before assignment. That's the part where the global keyword comes into play.

y = 20

def another_function():
    global y
    y += 1
    print(y)

another_function()

After declaring the y variable as global, we can run the script.

❯ python3 main.py
21

The script prints 21 and exits normally now.

Now consider the following code snippet.

def outer_function():
    z = 30

    def inner_function():
        z += 5
        print(z)

    inner_function()
    print(z)

outer_function()

We have the z variable being increased by 5 in the inner function. The variable itself is defined in the outer function, yet not in the global scope.

❯ python3 main.py
Traceback (most recent call last):
  File "main.py", line 12, in <module>
    outer_function()
  File "main.py", line 9, in outer_function
    inner_function()
  File "main.py", line 6, in inner_function
    z += 5
UnboundLocalError: local variable 'z' referenced before assignment

When executing the script we get the UnboundLocalError exception, similarly as in the previous situation. The remedy here is the nonlocal keyword. It is used within a nested function to indicate that a variable belongs to the nearest enclosing scope that is not global. This is particularly useful when we have nested functions and want to modify a variable in an outer (but non-global) scope.

def outer_function():
    z = 30

    def inner_function():
        nonlocal z
        z += 5
        print(z)

    inner_function()
    print(z)

outer_function()

Using nonlocal fixes the code.

❯ python3 main.py
35
35

Type hints

Type hints in Python are a way to provide optional type information for variables, function parameters, and return values. They help improve code readability and maintainability, and can also be used by tools and IDEs to catch potential type-related errors before runtime. While Python is dynamically typed, type hints allow you to indicate the expected types of values, making the code more self-explanatory and aiding developers in understanding how functions and variables are supposed to be used.

Type hints are not enforced by the Python interpreter itself, meaning that the code will still run even if the type hints are incorrect. However, tools like "mypy" can be used to statically analyze our code and provide type checking based on the hints.

Here's an example of using type hints:

def add_numbers(a: int, b: int) -> int:
    return a + b

result = add_numbers(5, 3)
print(result)

In this example:

  • a: int and b: int indicate that the parameters a and b are expected to be of type int.
  • -> int indicates that the return value of the function is expected to be an int.

Python supports various types for type hints, including basic types like int, str, float, and more complex types like List, Dict, Tuple, and even user-defined classes.

Build-in functions

As with many other programming languages, Python has its own set of functions shipped out of the box. These functions are available for use without requiring any additional imports or installations. They cover a wide range of tasks and operations, making it easier to perform common actions and manipulations in code. Here are some examples of frequently used built-in functions:

Type Conversion Functions:

  • int(): Converts a value to an integer.
  • float(): Converts a value to a floating-point number.
  • str(): Converts a value to a string.
  • list(): Converts an iterable to a list.
  • tuple(): Converts an iterable to a tuple.

Math Functions:

  • abs(): Returns the absolute value of a number.
  • round(): Rounds a floating-point number to a specified number of decimal places.
  • min(): Returns the smallest item in an iterable.
  • max(): Returns the largest item in an iterable.
  • sum(): Returns the sum of items in an iterable.

String Manipulation Functions:

  • len(): Returns the length of a string or iterable.
  • str.lower(): Converts a string to lowercase.
  • str.upper(): Converts a string to uppercase.
  • str.capitalize(): Capitalizes the first character of a string.
  • str.split(): Splits a string into a list based on a delimiter.

List and Tuple Functions:

  • len(): Returns the length of a list or tuple.
  • list.append(): Adds an item to the end of a list.
  • list.extend(): Extends a list by appending elements from an iterable.
  • list.pop(): Removes and returns the last item from a list.
  • tuple.index(): Returns the index of the first occurrence of a value in a tuple.

Input and Output Functions:

  • input(): Reads user input from the console.
  • print(): Displays output to the console.

Type Checking Functions:

  • type(): Returns the type of an object.
  • isinstance(): Checks if an object is an instance of a specified class.

Iterating and Enumerating Functions:

  • range(): Generates a sequence of numbers.
  • enumerate(): Returns an iterator that yields pairs of index and value.

File Handling Functions:

  • open(): Opens a file for reading or writing.
  • read(): Reads the contents of a file.
  • write(): Writes data to a file.

These are just examples, but provide a foundation for performing various operations and are an integral part of writing efficient and expressive Python code.

Summary

In this post, we learned the basics of functions in Python.

Functions are modular blocks of code designed to perform specific tasks. They encapsulate a set of instructions under a meaningful name, making the code more readable, reusable, and manageable. Functions are defined with the def keyword and can take multiple parameters and can also return multiple values.

Named parameters make code more self-explanatory, improve code readability, and reduce the potential for errors when calling functions with multiple parameters.

Default parameters are especially useful when you want to offer a sensible default behavior but still give callers the option to customize it. It can also simplify function calls by allowing you to omit arguments that use the default values.

Inner functions are frequently employed to contain logic that pertains solely to a particular context or to establish distinct behaviors for various instances of a function. This practice aids in maintaining organized, modular code that is simpler to comprehend, as it divides intricate tasks into smaller, more manageable components.

Type hints can be added to variables, function parameters, function return values, and even within classes. However, remember that type hints are optional and provide information for developers and tools, but they don’t change Python’s dynamic typing behavior.

Built-in functions are an integral part of Python and provide essential tools for a wide range of programming tasks. You can refer to the Python documentation for a comprehensive list and detailed information about each built-in function.

What's next?

Subscribe to receive more posts like this directly to your email!

Ideas💡, questions❔❓ ? Feel free to start the discussion 🗣️🎙️!

Python Basics: Tuples, Lists, Sets and Dictionaries
Tuples, lists, sets, and dictionaries are fundamental data structures that power our code. Let’s explore the unique traits of each: from immutability to dynamic resizing, from uniqueness enforcement to lightning-fast data retrieval.

Ready for challenges? 👇👇👇

Python Basics: Brain teasers
In the world of programming, the road to expertise is paved with challenges. Don’t shy away from solving programming problems; instead, embrace them as opportunities for growth.