In Python, you can check all available built-in method that a certain classes has using dir(). For example below we have a list class and we can check available methods:
Lists can be created by enclosing elements in square brackets [].
# Creating and manipulating a list of integersnumbers = [1, 2, 3, 4, 5]print("Original List:", numbers)
Original List: [1, 2, 3, 4, 5]
Lists are mutable, meaning their elements can be modified after the list is created using their index.
# Lists are mutable: elements can be changednumbers[0] =10# Changing the first elementprint(f"List after changing first element: {numbers}")
List after changing first element: [10, 2, 3, 4, 5]
You can access elements using their zero-based index.
# Accessing elements using their positionsecond_element = numbers[1]print(f"second_element: {second_element}")
second_element: 2
Slicing allows you to retrieve a sublist using the syntax [start:stop:step].
# Slicingfirst_three = numbers[:3]print(f"First three elements: {first_three}")# Slicing from and to a specific positionmiddle_three = numbers[1:4]print(f"Middle three elements: {middle_three}")# Slicing with a stepevery_other = numbers[::2]print(f"Every other element: {every_other}")
First three elements: [10, 2, 3]
Middle three elements: [2, 3, 4]
Every other element: [10, 3, 5]
A common trick to reverse a list is to use a negative step during slicing.
# Reversing a listreversed_list = numbers[::-1]print(f"Reversed List: {reversed_list}")
Reversed List: [5, 4, 3, 2, 10]
Negative indexing is highly useful for accessing elements relative to the end of the list.
# Accessing the last elementlast_element = numbers[-1]print(f"Last element: {last_element}")
Last element: 5
Python automatically resizes lists as elements are added or removed, managing memory for you dynamically.
# Discussing memory management# Python lists automatically resize as items are added or removednumbers.append(6)print(f"List after appending an element: {numbers}")
List after appending an element: [10, 2, 3, 4, 5, 6]
Unlike arrays in many other programming languages, Python lists can contain elements of mixed data types.
# Lists can contain different types of objectsmixed_list = [1, "Hello", 3.14, [2, 4, 6]]print("Mixed Type List:", mixed_list)
Mixed Type List: [1, 'Hello', 3.14, [2, 4, 6]]
Note
A list is suboptimal for frequent searches. Consider an array: while typically fixed in size, it offers faster access since elements are stored in a contiguous memory block.
Arrays are created using numpy. They are highly optimized for numerical operations compared to standard Python lists.
2
In contrast to list, appending to a numpy array actually allocates a brand new continuous block of memory and creates a new array (notice their different memory ids when printed).
3
Array mathematical operations are natively vectorized. For example, a + b performs element-wise addition implicitly without needing a for loop.
4
numpy provides numerous built-in summary statistics methods that can be computed across arrays efficiently.
a: [1 2 3], c: [1 2 3 4]
id: a -> 4635285456, id: c -> 4635285840
a + b = [5 7 9]
a * b = [ 4 10 18]
Mean: 5.5
median: 5.5
std: 2.8722813232690143
Dictionary
Each key in a Python dictionary has a harsh value that makes it extremly efficient for lookup using keys because a harsh value directly correspond to memory address, which makes searching for an item in a dictionary fast (in fact constant time) \((O(1))\). The drawback is that it requires more memory because they need to store the keys.
Basic operations
def main() ->None:# Creating and manipulating a dictionary person: dict[str|int, str] = {"name": "Arjan","profession": "Developer","city": "Utrecht", }print("Original Dictionary:", person)# Dictionaries are mutable: values can be changed based on their keys person["name"] ="Jane"# Changing the value associated with the key 'name'print(f"Dictionary after changing 'name': {person}")# Accessing elements using their keys profession = person["profession"]print(f"profession: {profession}")# Adding a new key-value pair person["profession"] ="YouTuber"print(f"Dictionary after adding a new key-value pair: {person}")# Removing a key-value pairdel person["city"]print(f"Dictionary after removing 'city': {person}")# Keys and values can be of different types person[1] ="One"print("Mixed Type Dictionary:", person)# Discussing memory management# Python dictionaries automatically resize and rehash as items are added or removed person["hobby"] ="Photography"print(f"Dictionary after adding a new hobby: {person}")# Getting a list of all keys and valuesprint(f"Keys: {person.keys()}")print(f"Values: {person.values()}")if__name__=="__main__": main()
Original Dictionary: {'name': 'Arjan', 'profession': 'Developer', 'city': 'Utrecht'}
Dictionary after changing 'name': {'name': 'Jane', 'profession': 'Developer', 'city': 'Utrecht'}
profession: Developer
Dictionary after adding a new key-value pair: {'name': 'Jane', 'profession': 'YouTuber', 'city': 'Utrecht'}
Dictionary after removing 'city': {'name': 'Jane', 'profession': 'YouTuber'}
Mixed Type Dictionary: {'name': 'Jane', 'profession': 'YouTuber', 1: 'One'}
Dictionary after adding a new hobby: {'name': 'Jane', 'profession': 'YouTuber', 1: 'One', 'hobby': 'Photography'}
Keys: dict_keys(['name', 'profession', 1, 'hobby'])
Values: dict_values(['Jane', 'YouTuber', 'One', 'Photography'])
Enums
from enum import IntEnum, StrEnum, autoclass HTTPStatus(IntEnum): OK =200 CREATED =201 ACCEPTED =202 NO_CONTENT =204 BAD_REQUEST =400 UNAUTHORIZED =401 FORBIDDEN =403 NOT_FOUND =404 INTERNAL_SERVER_ERROR =500 NOT_IMPLEMENTED =501class RESTMethod(StrEnum): GET = auto() POST = auto() PUT = auto() DELETE = auto()for status in HTTPStatus:print(f"{status.name}: {status.value}")print(f"Name of the enum member: {HTTPStatus.OK.name}")print(f"Enum member from value: {HTTPStatus(404)}")print(f"value of the enum member: {HTTPStatus.NOT_FOUND.value}")def response_description(status: HTTPStatus) ->str:if status == HTTPStatus.OK:return"Request succeeded"elif status == HTTPStatus.NOT_FOUND:return"Resource not found"else:return"Error occurred"def main() ->None: description = response_description(HTTPStatus.OK)print(description)if HTTPStatus.BAD_REQUEST == HTTPStatus.BAD_REQUEST:print("Both are client error responses")if HTTPStatus.INTERNAL_SERVER_ERROR in HTTPStatus:print("500 Internal Server Error is a valid HTTP status code.")if__name__=="__main__": main()
OK: 200
CREATED: 201
ACCEPTED: 202
NO_CONTENT: 204
BAD_REQUEST: 400
UNAUTHORIZED: 401
FORBIDDEN: 403
NOT_FOUND: 404
INTERNAL_SERVER_ERROR: 500
NOT_IMPLEMENTED: 501
Name of the enum member: OK
Enum member from value: 404
value of the enum member: 404
Request succeeded
Both are client error responses
500 Internal Server Error is a valid HTTP status code.
Tuples
Immutability: once you create a value in a tuple, you can not alter it.
from enum import StrEnumfrom typing import Union# Grouping several valuescoordinates = (10.0, 20.5)print(coordinates)class Month(StrEnum): JANUARY ="January" FEBRUARY ="February"def get_birthday() ->tuple[Month, int]:# Example function returning a tuple of month (StrEnum) and year (integer)return (Month.JANUARY, 1990)result = get_birthday()print(result)hash=hash(result) # Tuples are hashablemy_dict = {hash: result}print(my_dict)# Simple combination of a few variablesperson_info = ("John Doe", 30, "Engineer")print(person_info)# Tuples typically contain objects of different typesperson_details = ("Jane Doe", Month.FEBRUARY,1985,True,) # Name, birth month, birth year, residentprint(person_details)# Access by index, order mattersname, age, profession = person_infoprint(f"Name: {name}, Age: {age}, Profession: {profession}")month, year = get_birthday()print(f"Month: {month}, Year: {year}")# Tuples are not ideal for ordered collections of the same typenumbers = (1, 2, 3) # A list would be more suitable for this purpose# Define a command as a tuple with various possible typesCommand = Union[tuple[str, int], tuple[str, int, int], tuple[str]]def handle_command(command: Command):match command: case ("add", x, y):print(f"Adding {x} and {y}: {x + y}") case ("increment", x):print(f"Incrementing {x}: {x +1}") case ("reset",):print("Resetting to zero")case _:print("Unknown command")def main() ->None: handle_command(("add", 1, 2)) # Output: Adding 1 and 2: 3 handle_command(("increment", 10)) # Output: Incrementing 10: 11 handle_command(("reset",)) # Output: Resetting to zero handle_command(("unknown", 123)) # Output: Unknown commandif__name__=="__main__": main()