Introduction to Python

Python is a high-level interpreted programming language focused on general-purpose programming and code readability. It is also known for having the most simplistic Hello, world! program.

print("Hello, world!")

We recommend following the official Python documentation to learn more about the language. This manual serves as a guide to the most relevant concepts needed to work on the assignments. We expect you to be able to put together programming knowledge gained from other languages and the provided resources to fully master Python.

This course uses strictly the Python 3.11 version for compatibility reasons. Some features added in newer versions will not be available. We recommend following the official documentation for the supported Python version and ignoring new features.

Being an interpreted language, Python deals with blocks of code through indentation. Therefore, the interpreter is very picky about code style. Each new instruction must be indented perfectly with the others. Different levels of indentation may affect control flow. We recommend using a code formatter, usually provided with most code editors to regularly format your files.

Variables

Python is dynamically type-checked, which means that variables don't need to obey a fixed type. This paradigm gives the language a lot of flexibility but could come at the cost of complexity and difficulty in debugging.

# An example of a variable changing type in the same runtime context
my_var = "hello"
print(type(my_var)) # output: <class 'str'>
my_var = 10
print(type(my_var)) # output: <class 'int'>

To avoid this complexity, especially in debugging, Python provides a type hint feature, allowing us to specify the type of each variable. Type hints won't prevent the code above from being executed but will generate warnings inside your code editor, letting you know that something went wrong. We want to avoid situations in which variables change types in most cases, proving type hints to be extremely useful. Another benefit of this feature is intellisense: by providing the variable type, the code editor will be able to complete your instructions.

my_var: str = "hello"
print(type(my_var)) # output: <class 'str'>

# Warning: Type "Literal[10]" is not assignable to declared type "str"
my_var = 10 

# No warning, works fine
my_var = "world"

Control flow

By design, Python does not have an entry point to execution via a main function like other programming languages. You can start writing instructions from the first line, and the interpreter will execute them one by one. However, we still find value in following the main function pattern to starting execution.

def main() -> None:
    print("Hello, world!")

if __name__ == "__main__":
    main()

We will expand upon the meaning of if __name__ == "__main__" in the Modules section. For now, you may take this syntax for granted in writing your programs. The def main() -> None: represents the main function's signature; it takes no parameters and returns None (i.e., nothing, void).

Branches are handled in Python as expected using if statements. However, we do not need parentheses around any control flow statements, and the logical operators &&, ||, ! are replaced by and, or, not respectively.

some_var: str = "hi"
is_cool: bool = False
my_number: float = 3.14

if some_var == "hi" or my_number != 2.71 and not is_cool:
    print("python conditions")

Similarly, loops can be created using the while and for keywords. However, for loops slightly differ in syntax from other languages by requiring an iterator for execution. The most basic iterator is a range iterator.

for i in range(10):
    print(i)

Functions

Defining functions is easily done with the def keyword. Functions may take any number of parameters and return exactly one value.

# Example function definition. Make sure to include types in your function signature.
def sum(first: int, second: int) -> int:
    return first + second

Functions may raise exceptions for control flow, terminating execution and returning to the caller. This behavior is particularly useful for handling edge cases. We can check if a function encountered an error using a try ... except block.

The Python standard library is notorious for handling control flow based on exceptions. Always make sure to read the documentation of the function you are using and handle the exceptions accordingly.

def divide(first: int, second: int) -> float:
    if second == 0:
        raise ValueError("Can't divide by zero")
    return first / second
    
try:
    divide(1, 0)
except ValueError as e:
     print("An error occurred:", e)

Documentation is usually provided in the form of docstrings. It is recommended to document most of your function behavior, usage, exceptions, and parameters in the docstring rather than using comments.

# Docstring example. Code editors can parse docstrings into 
# nicely formatted documentation.
def multiply(first: int, second: int) -> int:
    """
    Multiply two numbers

    :param first: The first number
    :param second: The second number
    :return: The result of the multiplication
    """
    return first * second

Last updated