Python

Python is a popular general-purpose scripting language that is especially suited to web development.

All examples are based on Python 3.10.

Resources:

How to start

After installing Python, you can create a file with .py extension and run it with python command.

print("Hello, world")

Topics

Variables

# simple declaration
message = "Hello, world"

# multiple assignment
a = 10
b = 20
c = 30

# type hinting
age: int = 30
name: str = "Python"

Built-in types

# None
x = None

# Boolean
y = True
z = False

# Integer
a = 10
b = 100
c = -10

# Float
d = 10.5
e = 10.0
f = -10.0

# String
g = "Hello, world"
h = 'Hello, world'
i = """Hello,
world"""

# List
l = [1, 2, 3]

# Tuple
t = (1, 2, 3)

# Set
s = {1, 2, 3}

# Dictionary
d = {"a": 1, "b": 2, "c": 3}

# Range
r = range(1, 10)

Operators

# arithmetic operators
a = 10 + 5 # 15
b = 10 - 5 # 5
c = 10 * 5 # 50
d = 10 / 5 # 2
e = 10 % 3 # 1

# assignment operators
f = 10
f += 5 # 15
f -= 5 # 10
f *= 5 # 50
f /= 5 # 10
f %= 3 # 1

# comparison operators
g = 10 == 10 # True
h = 10 != 5 # True
i = 10 > 5 # True
j = 10 < 5 # False
k = 10 >= 10 # True
l = 10 <= 5 # False

# logical operators
m = True and True # True
n = True or False # True
o = not True # False

# increment and decrement operators
p = 10
p++ # 11
p-- # 10

# concatenation
q = "Hello" + " " + "world" # Hello world

# null coalescing
r = None ?? "no value" # no value

# null coalescing assignment
s = None
s ??= "no value" # no value

# spaceship operator
t = 10 <=> 5 # 1
u = 5 <=> 10 # -1
v = 10 <=> 10 # 0

# ternary
w = True ? "show if true" : "show if false" # show if true

# shorthand ternary
x = False ? "first" : "second" # first
y = 5 ? "first" : "second" # second
z = 5 ?: 0 # 5

# assignment
a = 10
a = 20 # a = 20

Type hinting

Python type hinting, introduced in Python 3.5 and enhanced in subsequent versions, is a way to indicate the expected types of variables, function parameters, and return values in your code.

# basic syntax
def greet(name: str) -> str:
    return f"Hello, {name}"

age: int = 30

# common types
from typing import List, Dict, Tuple, Optional

typed_list: List[int] = [1, 2, 3]
typed_dict: Dict[str, int] = {"a": 1, "b": 2, "c": 3}
typed_tuple: Tuple[int, str, float] = (1, "hello", 3.14)
typed_optional: Optional[str] = "hello"

# user defined types
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, {self.name}!")

def get_person_age(person: Person) -> int:
    return person.age

# union types
from typing import Union

def print_id(id: Union[int, str]):
    print(f"ID: {id}")

print_id(1)
print_id("10")

# any type
from typing import Any

def process_data(data: Any) -> None:
    pass

# type aliases
from typing import List, Tuple

Vector = List[float]
Matrix = List[Vector]

def scale_matrix(matrix: Matrix, factor: float) -> Matrix:

# callable types
from typing import Callable

def apply_operation(x: int, operation: Callable[[int], int]) -> int:
    return operation(x)

# generic types
from typing import TypeVar, List

T = TypeVar('T')

def first(l: List[T]) -> T:
    return l[0]

# final variables (Python 3.8+)
from typing import Final

MAX_SIZE: Final = 100

# Literal Types (Python 3.8+)
from typing import Literal

def move(direction: Literal['left', 'right', 'up', 'down']) -> None:
    pass

String

# basic declaration
string = "Hello, world"

# [+] operator
print("hello" + " " + "world") # hello world

# [*] operator
print("hello" * 3) # hellohellohello

# [in] operator
print("hello" in "hello world") # True

# [not in] operator
print("hello" not in "hello world") # False

# built-in string functions
print(len("hello")) # 5
print(str.upper("hello")) # HELLO
print(str.lower("HELLO")) # hello
print(str.capitalize("hello")) # Hello
print(str.title("hello")) # Hello
print(str.count("hello", "l")) # 2
print(str.find("hello", "l")) # 2
print(str.rfind("hello", "l")) # 5
print(str.index("hello", "l")) # 2
print(str.rindex("hello", "l")) # 5
print(str.replace("hello", "l", "x")) # hexxo

# build-in string methods
string = "hello world"
print(string.startswith("hello")) # True
print(string.endswith("world")) # True
print(string.upper()) # HELLO WORLD
print(string.lower()) # hello world

# string slicing
s = "hello world"
print(s[0]) # h
print(s[1:4]) # ell
print(s[1:]) # ello world

# string interpolation
carModel = "Toyota"
carYear = 2022

print(f"My {carModel} is {carYear}") # My Toyota is 2022

Collections

Arrays: are a fixed-size, ordered collection of elements of the same type.

from array import array

# array
my_array = array('i', [1, 2, 3, 4, 5])
my_array.reverse()
my_array.append(6)
my_array.extend([7, 8, 9])
print(type(my_array))

Lists: are a dynamic-size, ordered collection of elements of the same type.

# list
my_list = [1, 2, 3, 4, 5]
my_list.reverse()
my_list.append(6)
my_list.extend([7, 8, 9])
print(type(my_list))

Tuples: are a fixed-size, ordered collection of elements of the same type.

# tuple
my_tuple = (1, 2, 3, 4, 5)
my_tuple.count(2) # 2
my_tuple.index(2) # 1

Sets: are an unordered collection of unique elements.

# set
my_set = {1, 2, 3, 3, 4, 4, 5, 5, 5}
my_set.add(6)
my_set.remove(2)
my_set.union({6, 7, 8})
print(type(my_set))

Dictionaries: are a collection of key-value pairs.

# dictionary
my_dict = {"a": 1, "b": 2, "c": 3}
my_dict.update({"d": 4, "e": 5})
my_dict.pop("b")
my_dict.clear()
print(type(my_dict))

Conditional statements

# if statement
if x > 10:
    print("x is greater than 10")

# if-else statement
if x > 10:
    print("x is greater than 10")
else:
    print("x is less than or equal to 10")

# if-elif-else statement
if x > 10:
    print("x is greater than 10")
elif x < 10:
    print("x is less than 10")
else:
    print("x is equal to 10")

# short hand if
if a > b: print("a is greater than b")

# short hand if-else
print("a is greater than b") if a > b else print("a is less than or equal to b")

# and
if a > b and b > c:
    print("a is greater than b and b is greater than c")

# or
if a > b or b > c:
    print("a is greater than b or b is greater than c")

# not
if not a > b:
    print("a is not greater than b")

# pass statement
if a > b:
    pass

Match and Case Statements

x = 10

match x:
    case 1:
        print("x is 1")
    case 2:
        print("x is 2")
    case _:
        print("x is neither 1 nor 2")

Loops

# for loop
for i in range(1, 10):
    print(i)

# while loop
i = 1
while i <= 10:
    print(i)
    i += 1

# break statement
i = 1
while i <= 10:
    print(i)
    if i == 5:
        break
    i += 1

# continue statement
i = 1
while i <= 10:
    if i == 5:
        continue
    print(i)
    i += 1

List Comprehension: is a concise way to create lists based on existing lists.

numbers = [1, 2, 3, 4, 5]
squared_numbers = [x**2 for x in numbers]
print(squared_numbers)

Functions

# basic declaration
def greet(name):
    print(f"Hello, {name}!")

# default parameter
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Python") # Hello, Python!
greet("Bob", "Hi") # Hi, Bob!
greet(name="Python", greeting="Hi") # Hi, Python!

# variable length argument list
def sum(*numbers):
    total = 0
    for number in numbers:
        total += number
    return total

# keyword arguments
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

# *args and **kwargs
def print_args(*args, **kwargs):
    for arg in args:
        print(arg)
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_args(1, 2, 3, name="Python", age=30)

Lambda Functions: are anonymous functions that can be used in place of a function name.

# lambda function
multiply = lambda x, y: x * y
print(multiply(2, 3)) # 6

# lambda function with default parameter
add = lambda x, y=0: x + y
print(add(2, 3)) # 5

Classes

# basic class
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# methods
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, {self.name}!")

    def get_age(self):
        return self.age

# class attributes
class Car:
    wheels = 4  # Class attribute

    def __init__(self, make):
        self.make = make  # Instance attribute

# class methods
class Car:
    wheels = 4  # Class attribute

    def __init__(self, make):
        self.make = make  # Instance attribute

    @classmethod
    def get_wheels(cls):
        return cls.wheels

# static methods
class Calc:
    @staticmethod
    def add(x, y):
        return x + y

    @staticmethod
    def subtract(x, y):
        return x - y

    @staticmethod
    def multiply(x, y):
        return x * y

    @staticmethod
    def divide(x, y):
        return x / y

# getter and setter
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @property
    def name(self):
        return self._name.upper()

    @name.setter
    def name(self, value):
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value > 0:
            self._age = value
        else:
            raise ValueError("Age must be a positive number")

person = Person("Python", 30)
print(person.name)
person.name = "Bob"
print(person.name)

#encapsulation
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # "Private" attribute

    def deposit(self, amount):
        self.__balance += amount

Magic Methods: are special methods that are called automatically by the Python interpreter.

# magic methods
class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self): # convert to string
        return f"{self.title} by {self.author}"

    def __eq__(self, other): # check equality
        return self.title == other.title and self.author == other.author

person = Person("Python", 30)
print(person)

Inheritance: is a mechanism that allows a class to inherit the properties and methods of another class.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print(f"Hello, {self.name}!")

class Hero(Person):
    def __init__(self, name, age, power):
        super().__init__(name, age)
        self.power = power

    def fight(self):
        print(f"{self.name} is fighting with {self.power}!")

Metaclasses

Metaclasses are classes that define the behavior of other classes. They are used to customize the behavior of classes at runtime.

# metaclass
class MetaclassExample(type):
    def __new__(cls, name, bases, attrs):
        print(f"Creating a new class: {name}")
        return super().__new__(cls, name, bases, attrs)

class Example(metaclass=MetaclassExample): pass

Example # Output: Creating a new class: Example

Mixins

Mixins are a way to share code between classes. They allow you to define reusable functionality that can be included in multiple classes.

# mixin
class Printable:
    def print(self):
        print(self)

class Person(Printable):
    def __init__(self, name):
        self.name = name

person = Person("Python")
person.print() # Output: Person('Python')
import json

class SerializeMixin:
    def serialize(self):
        return json.dumps(self.__dict__)

class SaveableMixin:
    def save(self):
        # Code to save object to database
        pass

class User(SerializeMixin, SaveableMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email

user = User("Python", "python@example.com")
print(user.serialize())
user.save()

Dataclasses

Dataclasses are a special type of class that automatically generate methods for accessing and modifying the attributes of the class.

# basic usage
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p = Point(1, 2)
print(p)

# default values
@dataclass
class Person:
    name: str
    age: int = 30

# post-init processing
@dataclass
class Circle:
    radius: float
    area: float = 0.0

    def __post_init__(self):
        self.area = 3.14 * self.radius ** 2

# immutable dataclasses
@dataclass(frozen=True)
class ImmutablePoint:
    x: int
    y: int

# inheritance
@dataclass
class Animal:
    name: str

@dataclass
class Dog(Animal):
    breed: str

# field options
from dataclasses import dataclass, field
import random

@dataclass
class Point:
    id: int = field(init=False, default_factory=lambda: Point.random_id())
    x: int
    y: int

    @staticmethod
    def random_id():
        return random.randint(1, 100)

p = Point(1, 2)
p2 = Point(x=3, y=4)

print(p)
print(p2)

# comparison
@dataclass(order=True)
class Person:
    name: str = field(compare=False)
    age: int

# conversion to dictionary
@dataclass
class Point:
    x: int
    y: int

p = Point(1, 2)

print(asdict(p))

# slots
@dataclass(slots=True)
class Efficient:
    x: int
    y: int

Class Decorators

Class decorators are a way to modify the behavior of a class at runtime.

def add_greeting(cls):
    cls.greet = lambda self: print(f"Hello, {self.name}!")
    return cls

@add_greeting
class Person:
    def __init__(self, name):
        self.name = name

person = Person("Python")
person.greet()

Primer on Class Decorators

Error Handling

# example of a error handling function
def basic_error_handling(x, y):
    try:
        result = x / y
        print(f"Result: {result}")
    except ZeroDivisionError:
        print("Error: Division by zero")
    except Exception as e:
        print(f"Error: {e}")

# multiple exceptions in one block
def multiple_exceptions(x, y):
    try:
        file  = open("file.txt", "r")
        age = int(input("Enter your age: "))
    except (FileNotFoundError, ValueError) as Error:
        print(f"Error: {Error}")

# try-except-else-finally block
def complete_error_handling():
    try:
        file = open("example.txt", "w")
        file.write("Hello, world!")
    except:
        print("Error: Unable to open file")
    else:
        print("File opened successfully")
    finally:
        file.close()
        print("File closed")

# custom exception class
class AgeError(Exception):
    def __init__(self, message="Invalid age entered"):
        self.message = message
        super().__init__(self.message)

# using custom exception
def verify_age(age):
    try:
        age = int(age)
        if age < 0 or age > 150:
            raise AgeError("Age must be between 0 and 150")
        return f"Valid age: {age}"
    except ValueError:
        return "Please enter a numeric value"
    except AgeError as e:
        return str(e)

Modules

# importing a module
import math

# importing specific functions from a module
from math import sqrt, sin, cos

# importing all functions from a module
from math import *

# importing a module with an alias
import math as m

# importing a module and using it in the code
import math

print(math.sqrt(16)) # 4
print(sqrt(16)) # 4.0
print(m.sqrt(16)) # 4.0

# importing a module from a different directory
import sys
sys.path.append("path/to/directory")

# importing a module from a different directory with an alias
import sys
sys.path.append("path/to/directory")
from my_module import my_function

Custom modules

calc.py

def add(x, y):
    return x + y

def subtract(x, y):
    return x - y

def multiply(x, y):
    return x * y

def divide(x, y):
    return x / y

main.py

import calc

print(calc.add(2, 3)) # 5
print(calc.subtract(5, 2)) # 3
print(calc.multiply(4, 6)) # 24
print(calc.divide(10, 2)) # 5.0

Generators

Generators are a type of iterable, similar to lists or tuples, but with a key difference: they don’t store all the values in memory at once. Instead, they generate the values on the fly as they are needed.

# basic example of generator
def gen():
    yield 1
    yield 2
    yield 3

print(list(gen()))

# fibonacci sequence
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()

for _ in range(6):
    print(next(fib))

Iterators

Iterators are objects that allow you to iterate over a sequence of values, one at a time. They are used in a for loop to loop over a sequence of values.

# built-in iterator
my_list = [1, 2, 3, 4, 5]
my_iterator = iter(my_list)  # Creating an iterator

# using the iterator
print(next(my_iterator))  # Output: 1
print(next(my_iterator))  # Output: 2
print(next(my_iterator))  # Output: 3

# custom iterator
class CountDown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        else:
            self.current -= 1
            return self.current + 1

# create an iterator object
countdown = CountDown(5)

# using the iterator
for num in countdown:
    print(num)

Keywords

KeywordDescription
FalseBoolean value representing false.
NoneRepresents the absence of a value.
TrueBoolean value representing true.
andLogical operator for conjunction.
asUsed to create an alias in imports or exceptions.
assertFor debugging, tests a condition.
asyncDefines asynchronous functions.
awaitUsed with async to pause and resume execution.
breakExits out of a loop.
classDefines a new class.
continueSkips the rest of the current loop iteration.
defDefines a function.
delDeletes an object.
elifElse if; used in conditional statements.
elseDefines the alternative branch of a condition.
exceptCatches exceptions in a try block.
finallyExecutes code after try-except blocks, regardless of outcome.
forDefines a loop.
fromImports specific parts of a module.
globalDeclares a global variable.
ifStarts a conditional statement.
importImports a module or package.
inChecks if a value exists in a sequence.
isTests object identity.
lambdaDefines an anonymous function.
nonlocalDeclares a non-local variable in nested functions.
notLogical operator for negation.
orLogical operator for disjunction.
passA null statement; does nothing.
raiseRaises an exception.
returnReturns a value from a function.
tryTests code for errors.
whileDefines a while loop.
withSimplifies exception handling and resource management.
yieldPauses a function and returns an intermediate result.