Reader small image

You're reading from  Learn Python Programming, 3rd edition - Third Edition

Product typeBook
Published inOct 2021
Reading LevelBeginner
PublisherPackt
ISBN-139781801815093
Edition3rd Edition
Languages
Right arrow
Authors (2):
Fabrizio Romano
Fabrizio Romano
author image
Fabrizio Romano

Fabrizio Romano was born in Italy in 1975. He holds a master's degree in Computer Science Engineering from the University of Padova. He's been working as a professional software developer since 1999. Fabrizio has been part of Sohonet's Product Team since 2016. In 2020, the Television Academy honored them with an Emmy Award in Engineering Development for advancing remote collaboration.
Read more about Fabrizio Romano

Heinrich Kruger
Heinrich Kruger
author image
Heinrich Kruger

Heinrich Kruger was born in South Africa in 1981. He holds a master's degree in Computer Science from Utrecht University in the Netherlands. He has been working as a professional software developer since 2014. Heinrich has been working alongside Fabrizio in the Product Team at Sohonet since 2017. In 2020, the Television Academy honored them with an Emmy Award in Engineering Development for advancing remote collaboration.
Read more about Heinrich Kruger

View More author details
Right arrow

Functions, the Building Blocks of Code

"To create architecture is to put in order. Put what in order? Functions and objects."

– Le Corbusier

In the previous chapters, we have seen that everything is an object in Python, and functions are no exception. But what exactly is a function? A function is a sequence of instructions that perform a task, bundled together as a unit. This unit can then be imported and used wherever it is needed. There are many advantages to using functions in your code, as we'll see shortly.

In this chapter, we are going to cover the following:

  • Functions—what they are and why we should use them
  • Scopes and name resolution
  • Function signatures—input parameters and return values
  • Recursive and anonymous functions
  • Importing objects for code reuse

We believe the saying a picture is worth a thousand words is particularly true when explaining functions to someone who is new to...

Why use functions?

Functions are among the most important concepts and constructs of any language, so let us give you a few reasons why we need them:

  • They reduce code duplication in a program. By having a specific task be taken care of by a nice block of packaged code that we can import and call whenever we want, we don't need to duplicate its implementation.
  • They help in splitting a complex task or procedure into smaller blocks, each of which becomes a function.
  • They hide the implementation details from their users.
  • They improve traceability.
  • They improve readability.

Let us now look at a few examples to get a better understanding of each point.

Reducing code duplication

Imagine that you are writing a piece of scientific software, and you need to calculate prime numbers up to a certain limit—as we did in the previous chapter. You have a nice algorithm to calculate them, so you copy and paste it to wherever you need...

Scopes and name resolution

Do you remember when we talked about scopes and namespaces in Chapter 1, A Gentle Introduction to Python? We're going to expand on that concept now. Finally, we can talk in terms of functions, and this will make everything easier to understand. Let's start with a very simple example:

# scoping.level.1.py
def my_function():
    test = 1 # this is defined in the local scope of the function
    print('my_function:', test)
test = 0  # this is defined in the global scope
my_function()
print('global:', test)

We have defined the test name in two different places in the previous example—it is actually in two different scopes. One is the global scope (test = 0), and the other is the local scope of the my_function() function (test = 1). If we execute the code, we will see this:

$ python scoping.level.1.py
my_function: 1
global: 0

It's clear that test = 1 shadows the test = 0 assignment in my_function...

Input parameters

At the beginning of this chapter, we saw that a function can take input parameters. Before we delve into all the possible types of parameters, let's make sure you have a clear understanding of what passing an argument to a function means. There are three key points to keep in mind:

  • Argument-passing is nothing more than assigning an object to a local variable name
  • Assigning an object to an argument name inside a function doesn't affect the caller
  • Changing a mutable object argument in a function affects the caller

Before we explore the topic of arguments any further, please allow us to clarify the terminology a little. According to the official Python documentation:

"Parameters are defined by the names that appear in a function definition, whereas arguments are the values actually passed to a function when calling it. Parameters define what types of arguments a function can accept."

We will try to...

Return values

The return values of functions are one of those things where Python is ahead of the competition. In most other languages, functions are usually allowed to return only one object but, in Python, you can return a tuple—which implies that you can return whatever you want. This feature allows a programmer to write software that would be much harder to write in other languages, or certainly more tedious. We've already said that to return something from a function we need to use the return statement, followed by what we want to return. There can be as many return statements as needed in the body of a function.

On the other hand, if within the body of a function we don't return anything, or we invoke a bare return statement, the function will return None. This behavior is harmless when it's not needed, but allows for interesting patterns, and confirms Python as a very consistent language.

We say it's harmless because you are never forced to...

A few useful tips

When writing functions, it's very useful to follow guidelines so that you write them well. We'll quickly point some of them out.

Functions should do one thing

Functions that do one thing are easy to describe in one short sentence; functions that do multiple things can be split into smaller functions that do one thing. These smaller functions are usually easier to read and understand.

Functions should be small

The smaller they are, the easier it is to test and write them so that they do one thing.

The fewer input parameters, the better

Functions that take a lot of parameters quickly become hard to manage (among other issues).

Functions should be consistent in their return values

Returning False and returning None are not the same thing, even if, within a Boolean context, they both evaluate to False. False means that we have information (False), while None means that there is no information. Try writing functions that return...

Recursive functions

When a function calls itself to produce a result, it is said to be recursive. Sometimes recursive functions are very useful, in that they make it easier to write code—some algorithms are very easy to write using the recursive paradigm, while others are not. There is no recursive function that cannot be rewritten in an iterative fashion, so it's usually up to the programmer to choose the best approach for the case at hand.

The body of a recursive function usually has two sections: one where the return value depends on a subsequent call to itself, and one where it doesn't (called the base case).

As an example, we can consider the (hopefully now familiar) factorial function, N!. The base case is when N is either 0 or 1—the function returns 1 with no need for further calculation. On the other hand, in the general case, N! returns the product:

1 * 2 * ... * (N-1) * N 

If you think about it, N! can be rewritten like this...

Anonymous functions

One last type of function that we want to talk about are anonymous functions. These functions, which are called lambdas in Python, are usually used when a fully fledged function with its own name would be overkill, and all we want is a quick, simple one-liner that does the job.

Imagine that we wanted a list of all the numbers up to a certain value of N that are also multiples of five. We could use the filter() function for this, which will require a function and an iterable as input. The return value is a filter object that, when you iterate over it, yields the elements from the input iterable for which the function returns True. Without using an anonymous function, we might do something like this:

# filter.regular.py
def is_multiple_of_five(n):
    return not n % 5
def get_multiples_of_five(n):
    return list(filter(is_multiple_of_five, range(n)))

Note how we use is_multiple_of_five() to filter the first n natural numbers. This seems a bit excessive...

Function attributes

Every function is a fully fledged object and, as such, it has many attributes. Some of them are special and can be used in an introspective way to inspect the function object at runtime. The following script is an example that shows a part of them and how to display their value for an example function:

# func.attributes.py
def multiplication(a, b=1):
    """Return a multiplied by b. """
    return a * b
if __name__ == "__main__":
    special_attributes = [
        "__doc__", "__name__", "__qualname__", "__module__",
        "__defaults__", "__code__", "__globals__", "__dict__",
        "__closure__", "__annotations__", "__kwdefaults__",
    ]
    for attribute in special_attributes:
        print(attribute, '->', getattr(multiplication, attribute))

We used the built-in getattr() function to get...

Built-in functions

Python comes with a lot of built-in functions. They are available anywhere, and you can get a list of them by inspecting the builtins module with dir(__builtins__), or by going to the official Python documentation. Unfortunately, we don't have the room to go through all of them here. We've already seen some of them, such as any, bin, bool, divmod, filter, float, getattr, id, int, len, list, min, print, set, tuple, type, and zip, but there are many more, which you should read about at least once. Get familiar with them, experiment, write a small piece of code for each of them, and make sure you have them at your fingertips so that you can use them when needed.

You can find a list of built-in functions in the official documentation, here: https://docs.python.org/3/library/functions.html.

Documenting your code

We are big fans of code that doesn't need documentation. When we program correctly, choose the right names, and take care of the details, the code should come out as self-explanatory, with documentation being almost unnecessary. Sometimes a comment is very useful though, and so is some documentation. You can find the guidelines for documenting Python in PEP 257 -- Docstring conventions:

https://www.python.org/dev/peps/pep-0257/, but we'll show you the basics here.

Python is documented with strings, which are aptly called docstrings. Any object can be documented, and we can use either one-line or multi-line docstrings. One-liners are very simple. They should not provide another signature for the function, but instead state its purpose:

# docstrings.py
def square(n):
    """Return the square of a number n. """
    return n ** 2
def get_username(userid):
    """Return the username of a user given...

Importing objects

Now that we know a lot about functions, let's look at how to use them. The whole point of writing functions is to be able to reuse them later, and in Python, this translates to importing them into the namespace where you need them. There are many different ways to import objects into a namespace, but the most common ones are import module_name and from module_name import function_name. Of course, these are quite simplistic examples, but bear with us for the time being.

The import module_name form finds the module_name module and defines a name for it in the local namespace, where the import statement is executed. The from module_name import identifier form is a little bit more complicated than that but basically does the same thing. It finds module_name and searches for an attribute (or a submodule) and stores a reference to identifier in the local namespace. Both forms have the option to change the name of the imported object using the as clause:

...

One final example

Before we finish off this chapter, let's go through one last example. We could write a function to generate a list of prime numbers up to a limit; we've already seen the code for this in Chapter 3, so let's make it a function and, to keep it interesting, let's optimize it a bit.

It turns out that we don't need to divide by all numbers from 2 to N-1 to decide whether a number, N, is prime. We can stop at √N (the square root of N). Moreover, we don't need to test the division for all numbers from 2 to √N, as we can just use the primes in that range. We leave it up to you to figure out why this works, if you're interested in the beauty of mathematics.

Let's see how the code changes:

# primes.py
from math import sqrt, ceil
def get_primes(n):
    """Calculate a list of primes up to n (included). """
    primelist = []
    for candidate in range(2, n + 1):
        is_prime...

Summary

In this chapter, we explored the world of functions. They are very important and, from now on, we'll use them in virtually everything we do. We talked about the main reasons for using them, the most important of which are code reuse and implementation hiding.

We saw that a function object is like a box that takes optional inputs and may produce outputs. We can feed input arguments to a function in many different ways, using positional and keyword arguments, and using variable syntax for both types.

You should now know how to write a function, document it, import it into your code, and call it.

In the next chapter we will be picking up the pace a little a bit, so we suggest you take any opportunity you get to consolidate and enrich the knowledge you've gathered so far by putting your nose into the Python official documentation.

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Learn Python Programming, 3rd edition - Third Edition
Published in: Oct 2021Publisher: PacktISBN-13: 9781801815093
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Authors (2)

author image
Fabrizio Romano

Fabrizio Romano was born in Italy in 1975. He holds a master's degree in Computer Science Engineering from the University of Padova. He's been working as a professional software developer since 1999. Fabrizio has been part of Sohonet's Product Team since 2016. In 2020, the Television Academy honored them with an Emmy Award in Engineering Development for advancing remote collaboration.
Read more about Fabrizio Romano

author image
Heinrich Kruger

Heinrich Kruger was born in South Africa in 1981. He holds a master's degree in Computer Science from Utrecht University in the Netherlands. He has been working as a professional software developer since 2014. Heinrich has been working alongside Fabrizio in the Product Team at Sohonet since 2017. In 2020, the Television Academy honored them with an Emmy Award in Engineering Development for advancing remote collaboration.
Read more about Heinrich Kruger