Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Functions, exceptions, modules

Faculty of Mechanical Engineering, University of Ljubljana

Functions

The print function

print is one of the most frequently used functions. It prints all the arguments passed to it, puts a space between them, and at the end moves to a new line.

Let’s look at an example:

first argument second = 5.0

For more general use, see the documentation:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False),

where the arguments are:

  • *objects represents the arguments we want to print (the full meaning of the asterisk will become clear when we discuss functions, below),

  • sep is the separator string,

  • end is the terminating string,

  • file is the output file (sys.stdout is the standard output, which in this particular case means the screen, i.e. the command line; we will change this later),

  • flush, when True, finalizes the output (otherwise, due to optimization, it may remain in the buffer).

For the end argument above we used a so-called escape character: \.

The purpose of the escape character is to give the following character a special meaning; some common uses are:

  • \n inserts a line break,

  • \t inserts a tab,

  • \b deletes the previous character (backspace),

  • \’ prints a single quote ',

  • \" prints a double quote ",

  • \\ displays the backslash,

  • \ at the end of a line: the string continues on the next line (without a newline character).

Example (how we usually do not program):

fi-ve=5:)

And one more example with escape characters:

Strings in Python (per PEP8) should not be longer than 79 characters. The reason is that we do not have to scroll the window left and right. So that we can still write strings that are longer than 79 characters, we use a single separator character \ (note how it is printed: \)

String formatting

For formatting strings we have several tools available:

Let’s look at some examples:

pi = 3.14
pi = 3.14
pi = 3.14

The f-string has been implemented since Python version 3.6 (PEP 498) and is the simplest way of formatting. We mention the other approaches only so that the reader becomes acquainted with them and so that we can link to the documentation.

An f-string always begins with f before the first quote and allows very general formatting (see the documentation).

The value we want to format inside the string is placed in curly braces:

f'text... {arg_name1:fmt1} ... text...'

Inside the curly braces:

  • before : we give the name of the value we want to format

  • after : we format how the variable is printed; most often we will use the fmt forms:

    • w.df - floating-point representation (float)

    • w.de - representation with an exponent (exponent)

    • w.dg - general format (general)

    • ws - string of characters (string).

w is the (minimum) total width, and d is the number of digits after the decimal point.

Let’s look at an example:

'Marko is 1.84 m tall, or also: 1.840000e+00 m, 1.840 m.'

We could do the same with the format method by referring to the argument index:

'Marko is 1.84 m tall, or also: 1.840000e+00 m, 1.840 m.'

Example using a dictionary:

'height = 5.000, density = 1.001e+02'

Formatting with fmt (the Format Specification Mini-Language) is extremely rich. The part after the colon has the general form (see the documentation):

[[fill]align][sign][#][0][width][grouping_option][.precision][type]

where the parameters in square brackets are optional (all of them are optional):

  • fill can be any character,

  • align specifies the alignment, options: "<" | ">" | "=" | "^",

  • sign specifies the sign, options: "+" | "-" | " ",

  • width defines the minimum total width,

  • grouping_option grouping options, options: "_" | ","

  • precision defines the number of digits after the decimal separator,

  • type defines how the data should be displayed, options: "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | ""o" | "s" | "x" | "X" | "%"

We align text to the left with "<", to the right with ">", and centered with "^". Example of centered/right alignment (width 20 characters):

'********1.84********'
'_________________5.0'
'The complex number (4-2j) consists of a real (4.0) and an imaginary (-2.0) part.'

You can look at formatting of date and time in the appendix below.

File handling

File handling is important, since we often store data in files or read it from them. A file we want to read from or write to must first be opened. We do this with the open command (documentation):

open(file, mode='r', buffering=-1, encoding=None,
    errors=None, newline=None, closefd=True, opener=None)

Of all the arguments, only file is required; it defines the path to the file.

The mode argument can be:

  • 'r' for reading (read, default),

  • 'w' for writing (write, the file is cleared first),

  • 'x' for exclusive writing; if the file already exists it returns an error,

  • 'a' for appending to the end of an existing file (append),

  • 'b' binary format,

  • 't' text format (default),

  • '+' the file is opened for both reading and writing.

The last three ('b', 't', '+') are used in combination with the first three ('r', 'w', 'x'). Example: mode='r+b' means reading and writing a binary file.

We will look in more detail only at reading and writing text files.

The other (optional) arguments of the open function are described in detail in the documentation.

The default is mode='r' (i.e. 'rt', since text mode is the default), which means the file is opened for reading in text form.

Let’s first look at writing to a (text) file (mode='w'):

'data/prikaz vpisa.txt' is a relative path (relative to the location where the Jupyter notebook is run) to the file.

Let’s write the first line using the write method:

23

The write method returns the number of characters written. If you check the contents of the file at this moment, it is very likely still empty. The reason is that the content is still in the buffer, which is flushed to disk only occasionally or when the file is closed (the reason for this behavior is the speed of writing to disk; for details see the documentation of the buffering argument of the open function).

Let’s write the second line:

12

Now let’s also enter some numerical values:

We close the file:

Now we open the file for reading and writing, mode='r+':

Let’s check all the lines with a for loop; in the code below, file returns one line on each iteration:

test of the first line
second line
0.00e+00	 0.0000e+00
1.00e+00	 1.0000e+00
2.00e+00	 2.0000e+00
3.00e+00	 3.0000e+00
4.00e+00	 4.0000e+00

In the introduction to the print function we mentioned the file argument, which defines the file to write to (the default is the standard output, i.e. the screen). If we pass as the file argument a file that is opened for writing:

the content is written to the file. You can check the result in the file!

This is a nice opportunity to learn about the with statement (documentation). The full scope of the with statement goes beyond the purpose of this course, but its use in file handling is very simple, so let’s look at an example:

test of the first line
second line
0.00e+00	 0.0000e+00
1.00e+00	 1.0000e+00
2.00e+00	 2.0000e+00
3.00e+00	 3.0000e+00
4.00e+00	 4.0000e+00
end

The with statement stores the result of the open function in (as) the name file, and then we print the values for each line. When exiting the with statement, the file.close() method is called automatically. The with statement therefore allows us to write clear and compact code.

Functions in general

Python has many functions already built in (see the documentation); but we can also write functions ourselves or import them from so-called modules (above we imported datetime, more on this later).

A generic example of a function is (documentation):

def function_name(parameters):
    '''docstring'''
    [code]
    return content

Here we point out:

  • def denotes the definition of a function,

  • function_name per PEP8, function names are written in lowercase, with multiple words joined by an underscore,

  • parameters the parameters of the function, more on which below,

  • docstring (optional) the documentation of the function,

  • [code] the code of the function,

  • return (optional) the command to exit the function; whatever follows is returned as the result (if there is no return, or nothing after return, then None is returned)

Example of a simple function:

20

Passing arguments to functions

Python knows two ways of passing arguments:

  • by position (positional)

  • by name.

Let’s look at both ways with an example:

18
18

It is recommended to pass arguments by name; this reduces the chance of error!

If we do not give a name, then the name is assumed based on the order of the arguments; example:

12

When we mix positional and named argument passing, the positional arguments must come first, and only then the named ones. At the same time, a named argument must not redefine a positional one; since length is the first argument, calling code like this:

area(3, length=4)

would cause an error.

Example of good practice when defining arguments

We often extend functions and add new arguments, or sometimes we cannot even define the number of arguments to a function in advance!

Python has no problem with this. Arguments that are not explicitly defined are handled with:

  • *[name0] the tuple [name0] contains all positional arguments that are not explicitly defined,

  • **[name1] the dictionary [name1] contains all named arguments that are not explicitly defined.

Let’s look at an example:

Student Janez has registered for the exam: True.

Now let’s prepare a second, upgraded version (it allows the same use as the first version):

Student Janez has registered for the exam: True.
Student Janez has registered for the exam: True.
figure:)
Passing functions as an argument

Here we would like to point out that arguments can also be other functions.

Let’s look at an example:

So we have two functions, format_a and format_b, which format a numerical value slightly differently.

Let’s look at their use:

Display of value: 3
Display of value: 3.00

Function docstring

The docstring is an optional part of a function definition that documents the function and its use. From this point of view, it is highly recommended to use it.

The documentation for the docstring lists the essential elements:

  • the first line should be a short summary of the function,

  • if there are multiple lines, the second should be blank (to visually separate the first line from the rest of the text),

  • use three quotes (double " or single '), which denote a multiline string of characters.

Example of a documented function:

Example of a call with default arguments (while calling the function you can press [shift]+[tab] to access the help):

10

Local/global names

A simple rule of thumb for functions is: a function sees out, but others do not see in. A function has internal names that do not collide with the same names in other functions.

Let’s look at an example:

This is the outer value: 3, and this is the inner one: 5

Since the name inner is not defined outside the function first, calling:

inner

outside the function would return an error (we cannot see into the function).

Here it is worth mentioning that passing external names into a function outside of the function’s arguments is discouraged.

return

The return command returns values from a function. So far we have seen results in the form of a single variable; but this variable can also be a tuple.

Let’s look at an example:

(1, 2, 3)

We can capture the result of the function like this:

(1, 2, 3)

Or by unpacking the values (this is generally true for tuples):

1

Anonymous function/expression

An anonymous function (see the documentation) is a function to which we do not give a name (which is why it is anonymous), but instead define it with the lambda command. A typical use is:

lambda [parameter list]: expression

We will look at the lambda function with a sorting example:

from the smallest to the largest value. We can do this with the built-in sort method (documentation):

sort(*, key=None, reverse=False)

The method has two optional arguments:

  • key the key by which we sort the elements

  • reverse, if False, the comparison between key values uses <, otherwise >.

Note: list.sort() performs the sorting on the existing list and does not return a list. The sorted() function (see the documentation), however, returns a list of the sorted values.

Let’s look at an example:

[-8, -4, 3, 6]

Now let’s use a key where the squared value is used for comparing the sorting values:

[3, -4, 6, -8]

Handling exceptions

While programming, exceptions occur relatively often; for example: division by zero.

1/0

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-50-05c9758a9c21> in <module>()
----> 1 1/0

ZeroDivisionError: division by zero

In case of an exception, Python warns about it and interrupts execution. It is not good for a program to be interrupted or to crash; for this reason a beginner programmer would probably handle the above situation with an if statement. However, such an approach is neither very practical nor general, and that is why exception handling has become established in most modern programming languages.

We will look at some basics that will above all make it easier for students to read and understand the code of other authors.

We handle exceptions with the try statement:

try:
    [code0]
except:
    [code1]
finally:
    [code2]

In the try statement, except is executed if an exception occurs in the code [code0]; finally is optional and is executed in any case when exiting the try statement.

Let’s look at an example where we simply continue execution:

We use the pass command when the syntax requires a statement (code) to execute, but we do not want any action (see the documentation).

With except we can also catch different exceptions; we do it like this:

Division by zero!

Now let’s use the same code for the case of dividing a number by a string:

Unexpected error.

Raising exceptions

We can also raise exceptions ourselves; we do this with the raise command (documentation).

An example of raising an error is:

raise Exception('Description of the exception')

a simple but complete example, where we would expect person_name to be of type str:

The name is not of the expected type `str`.

Basics of modules

With modules we can easily reuse already-written code. Simply put, modules in Python allow us to relatively easily maintain clarity and order. Usually we package some self-contained function, a group of functions, or, for example, an object into a module (later we will learn what an object is).

For a detailed description of importing modules, see the documentation.

In the modules folder there is a file first_module.py with the following content:

def square(x=1):
    return x**2

Important: we often arrange modules hierarchically into directories; a directory of modules is called a package. If the directory contains a file (it can even be empty) named __init__.py, it is a regular package; since Python 3.3, however, a directory behaves as a package even without it (a so-called namespace package). In the example above, the modules folder thus behaves as a package, and in it there is the module first_module (the file first_module.py).

We typically import modules in two ways:

First way:

from modules import first_module

in this case we import the namespace first_module; this means that we call the function square() like this: first_module.square().

Second way of importing a module:

import modules.first_module

We import the namespace modules.first_module; this means that we call the function square() like this: modules.first_module.square(). (The command import modules alone does not import submodules automatically.)

If we want to import the functions in the first_module module, we do it like this:

Now we can call the square function like this:

25

With the from command we can import only part of a module. Example:

Now we call the square function directly:

36

We can also rename functions or modules. Example:

49

Where does Python look for modules?

  1. In the current folder (the one in which you launched jupyter notebook),

  2. in the folders defined in PYTHONPATH,

  3. in the folders where Python is installed (the site-packages subdirectory).

Importing modules and the Jupyter notebook

In this book, for pedagogical reasons, we will always import modules at the point where we first need them. The rules of good practice and clear code recommend that the imports of all modules be done at the top of the Jupyter notebook!

The pickle module

We will often use the pickle module for very fast and compact storing and reading of data. pickle stores the values of a variable in the same form as they are written in memory. In the case of real numbers, it therefore does not lose accuracy (writing to text form loses accuracy, since we define the number of printed digits, which can be smaller than the number of decimal places in memory). In addition, the representation in memory is usually less demanding than the representation in text form. Since the values do not need to be converted to/from text form, writing/reading is also very fast! Here we will look at the basic usage; pickle is described comprehensively in the documentation.

pickle has two important methods:

  • dump(obj, file, protocol=None, *, fix_imports=True) for storing the object obj,

  • load(file, *, fix_imports=True, encoding="ASCII", errors="strict") for reading the file file.

We will look at the usage with an example. First we import the module:

Let’s write the values of the dictionary a to the file data/stanje1.pkl:

Note that the file was opened for writing in binary form wb. It is also important that we use the latest version of the protocol (the protocol parameter); the default one is not optimized for speed and does not write in binary form.

Now let’s read the values back into b and display it:

{'number': 1, 'size': 10}

Above we claimed that the approach is very fast; let’s verify this.

First we prepare the data (a list of 50000 values of type int):

Writing in text form

25.1 ms ± 394 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Writing with pickle:

988 μs ± 50 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

That is almost a 20-fold speedup!

Using modules from the internet

In addition to the built-in modules and those we prepare ourselves, there is also a large set of modules that we can find on the internet and that add new functionality to Python. It is these modules that make the Python ecosystem so useful.

Technically, a module is a single file; when some larger module contains several modules, we can start talking about packages. We will learn about the most important packages in the field of engineering sciences within this book.

We can update modules or packages, or install new ones; for this we are helped by so-called package managers. The most common package manager is pip (documentation).

The pip package manager

pip is a package manager with a long history and supports a large number of packages (pypi.org).

We find already installed packages with a command in the command line (documentation):

pip list

Instead of output to the command line, we will often use the option of the Jupyter notebook where, with an exclamation mark (!) in a code cell, we execute a command in the command line (this is a short form of the magic command %sx - shell execute, see the documentation). Note: two exclamation marks would return the entire result directly into the notebook (since the output is very long, we avoided this).

Let’s look at an example:

['Package Version', '------------------ -------------------------------', 'attrs 26.1.0', 'btrfsutil 7.1', 'certifi 2026.7.22']

We install packages with the command (in the command line):

pip install [package_name]

we update with:

pip install [package_name] --upgrade

and we remove with:

pip uninstall [package_name]

For more, see the documentation.

Example of installing a package

Otherwise, we most often find packages on the internet (pypi.org). Go to the mentioned portal and look for packages on the topic of downloading videos from www.youtube.com. By searching for “youtube download” we find a promising and well-maintained package yt-dlp, which we install (from the command line):

pip install yt-dlp

Now let’s use the module (for usage details see the documentation). First we import the entire package:

Additional

Formatting date and time

First we import the module for working with date and time, datetime, and display the current time with the date (we use the now() function):

datetime.datetime(2026, 9, 25, 15, 0, 35, 454864)

We format the time information:

'2026-09-25 15:00:35'

Formatting date and time follows these methods:

  • strftime (documentation) is used to convert from datetime to a string of characters,

  • strptime (documentation) is used to convert from a string of characters to the datetime type.

How to print the appropriate form (i.e. how to use the characters %Y, %m, etc.) is likewise explained in detail in the documentation.

Let’s look at an example of converting the datetime type to a string (relative to the above, we added %a so that the name of the day is printed):

'2026-09-25 15:00:35 Fri'

Now in the opposite direction, i.e. from a string to datetime:

datetime.datetime(2017, 9, 28, 5, 31, 45)

Some modules

  1. openpyxl for writing and reading Excel xlsx/xlsm files: source,

  • Regular expressions (a very powerful tool for searching through strings): source.

The sys module

Python has a large number of built-in modules (and many more can be downloaded from the internet, e.g. here) that add various functionalities.

The sys module provides access to objects used by the interpreter.

Let’s look at an example; first we import the module:

We can check the folders where modules are searched for with the command sys.path (documentation):

['c:\\_j\\Work Janko\\Pedagosko delo\\_predavanja\\Programiranje in numericne metode\\pynm\\notebooks', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311\\python311.zip', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311\\DLLs', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311\\Lib', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311', '', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages\\win32', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages\\win32\\lib', 'C:\\Users\\janko\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\site-packages\\Pythonwin']

The os module

The os module is intended for working with the operating system and takes care of compatibility across different operating systems (if, for example, you write a program on a Windows system, it will also work on a linux system).

We import the module:

We look at the current folder:

'.'

or the current folder in absolute form:

'c:\\_j\\Work Janko\\Pedagosko delo\\_predavanja\\Programiranje in numericne metode\\pynm\\notebooks'

How do we find all the files and folders in a directory?

Let’s display the first four with the ipynb extension:

I found a file: Lecture 01 - Introduction to Python.ipynb
I found a file: Lecture 09 - Root finding.ipynb
I found a file: Lecture 08 - Approximation.ipynb
I found a file: Lecture 14 - Testing code correctness, user interface.ipynb

For more, see the documentation.