---
tags:
  - notes
  - programming
author: TrudeEH
draft: false
showToc: true
title: Python
---

## Python3 [Documentation](https://docs.python.org/3/)

- `number`: Replace with a number.
- `*object`: Any number of objects.
- `[, ndigits]`: Anything between `[]` is optional.

## Run Python Code

Python is an interpreted language. It is not compiled like C.

```Shell
python hello.py
```

Skipping the compiling step makes the language easier to use but at the cost of performance.

## Print

Print information on the console.

```Python
print("hello, world")
answer = "Some Text"
print(f"Your answer is: {answer}") # formatted string (any type)
print("Your answer is: " + answer) # answer must be string
print("Your answer is:", answer) # answer can be any type
print("Same line", end="")
print("!" * 4) # "!!!!
# '' and "" do the same.
```

## Input

Prompt the user for information.

```Python
# String Input
name = input("Prompt: ")
number = int(input("Prompt: "))
name = name.strip() # Remove whitespaces from a string
```

## Variables

Python infers the type of variable automatically.

```Python
answer = "Some Text"
counter = 0
counter = counter + 1
counter += 1
# Counter++ does not exist in Python
```

### Global Variables

Use the global keyword to modify a global variable while outside its scope.

```Python
balance = 0
def deposit(n):
	global balance
	balance += n
```

## Comments

```Python
# Comment
"""
Multi
Line
Comment
"""
```

### Docstring

The documentation for a function.  
This can later be used to generate manuals and documentation Some IDEs do so automatically.

```Python
def code():
	"""
	Does something.
	"""
	...
```

### Type Hints

Type hints are not enforced. These can be read by debuggers, but Python itself allows these types to be violated.

```Python
def meow(n: int) -> None: # -> None indicates that the function does not return a value
	...
number: int = int(input("Number: "))
```

## Data Types

| Data Type Category | Data Types                         |
| ------------------ | ---------------------------------- |
| Text Type          | `str`                              |
| Numeric Types      | `int`, `float`, `complex`          |
| Sequence Types     | `list`, `tuple`, `range`           |
| Mapping Type       | `dict`                             |
| Set Types          | `set`, `frozenset`                 |
| Boolean Type       | `bool`                             |
| Binary Types       | `bytes`, `bytearray`, `memoryview` |
| None Type          | `NoneType`                         |

> `float` types can overflow, much like C, but `int` never does, and instead grows as large as needed to fit a given value.

### Type Conversion

```Python
int("1") # convert to int
float(2) # convert to float
```

### Data Type Functions

```Python
round(5.12) # round a number
round(3.43, 2) # round to the 2nd decimal place
len(list) # length of a list (nº of elements)
```

### Unpack Values

Values can be split using `*`. This method works with lists, dictionaries, and tuples.  
For dictionaries, `*dict` returns key names and `**dict` returns the value names.  
Unpack can also be used when returning values from functions.

```Python
def total(gold, silver, copper):
	return gold * 100 + silver * 10 + copper
coins = [100, 50, 25]
print(total(*coins), "Copper coins")
```

### F Strings

A string that executes formatting.

```Python
f'Some text: {1+1}' # Executes 1+1 and prints "Some text: 2"
f'{z:.2f}' # Variable z is rounded to the 2nd decimal place.
```

### Ranges

Produce a range of numbers.

```Python
range(10) # 1, 2, 3... 10
if 80 <= score < 90:
	code...
```

### Lists

Arrays are flexible in Python, unlike C. Elements can be added and removed at runtime.

```Python
scores = [72, 73, 33]
scores.append(32)
# scores += [43, 45] # Adds a list to another list
average = sum(scores) / len(scores)
print(f"Average: {average}")
scores[1:] \#Slice the list -> From 1 to the end. (Skips element 0)
scores[1:-1] \#Slice the list -> From 1 to the end. (Skips element 0 and last)
```

#### List Comprehension

Create a new list with a function applied to every element of the list.

```Python
words = ["This", "is", "CS50"]
uppercased = [word.upper() for word in words]
print(*uppercased)
```

In this example, every element in `words` is uppercased.  
This feature is useful to filter items as well:

```Python
students = [
	{"name": "Trude", "class": "code"},
	{"name": "Maxs", "class": "art"},
	{"name:" "JCionx", "class": "code"},
]
codeStudents = [
	student["name"] for student in students if student["class"] == "code"
]
```

#### Filter Function

Similar code, but for a more functional oriented programming approach.

```Python
def is_class(s):
	return s["class"] == "code"
coders = filter(is_class, students)
```

### Sets

Similar to a list, but can only contain unique items.

```Python
values = set(1, 1, 1, 2)
print(values) # 1, 2
```

### Dictionaries

A list, but instead of using an index, uses keywords.

```Python
students = {
	"Key1": "Value",
	"Key2": "Value",
	"Key3": "Value",
}
print(students["Key1"])
```

#### Loop over Dictionary

```Python
for student in students:
	print(student)           # Iterates over keys.
	print(students[student]) # Iterates over values.
```

#### Dictionary Comprehension

Filter dictionaries.

```Python
students = ["Trude", "Maxs", "JCionx"]
# Classic for
coders = []
for student in students:
	coders.append({"name": student, "class": "code"})
# List comprehension (3 dicts in a list)
coders = [{"name": student, "class": "code"} for student in students]
# Dict comprehension (1 dict with name: class for all 3)
coders = {student: "code" for student in students}
```

### Hashtable

```Python
table = [
	{"Key1": "Value", "Key2": "Value", "Key3": "Value"},
	{"Key1": "Value", "Key2": "Value", "Key3": "Value"},
	{"Key1": "Value", "Key2": "Value", "Key3": "Value"},
	{"Key1": "Value", "Key2": "Value"},
]
print(table[0]["Key2"])
for tab in table:
	print(tab["Key1"]) # Print all Key1 values.
```

## Conditions

### IF

```Python
if x < y:
	print("x is less than y")
elif x > y:
	print("x is greater than y")
else:
	print("x is not less than y")
if test in numbers:
	code...
```

`()` are optional in the condition.

### `:=` Operator

Syntax sugar to integrate an assignment operator with an IF statement.  
**Without it**

```Python
matches = 1 == 1
if matches:
	print(matches) # True
```

**With it**

```Python
if matches := 1 == 1:
	print(matches) # True
```

## Loops

### While

```Python
while True:
	print("meow")
i = 0
while i < 3:
	print("meow")
	i += 1
```

### For

#### Loop through an Array

```Python
for _ in [0, 1, 2]:
	print("Hello World.")
# _ throws away the value (no variable).
```

#### Loop through a Range

```Python
for i in range(3):
	print("Loops from 0 - 3")
```

#### Loop through an Array, Using Its Value as the Index

```Python
students = ["one", "two", "three"]
for student in students:
	print(student)
```

#### Enumerate

Iterate over a sequence, and get the current value and index.

```Python
students = ["Trude", "Maxs", "JCionx"]
for i, student in enumerate(students):
	print(i + 1, student)
```

### Map

Execute a function on every element of a list.  
Takes two arguments:
- Name of a function
- Data structure to apply the function on

```Python
words = ["This", "is", "CS50"]
uppercased = map(str.upper, words)
print(*uppercased)
```

### Keywords

- `break`: leave the loop
- `continue`: skip to next iteration

## Libraries

```Python
import lib
lib.component(...)
from lib import component
component(...)
```

### Packages

A package is a collection of libraries in the same directory, often built by other developers.  
You can download user packages on [pypi.org](http://pypi.org/).

#### Install Packages with Pip

`pip` is a package manager for Python.

```Bash
pip install package
```

### Useful Libraries

- `random` Generate random numbers
    - `shuffle`
    - `choice`
    - `randint`
- `statistics`
    - `mean`
- `requests` Web requests (similar to `curl`) useful for interfacing with APIs

#### APIs

APIs usually communicate in JSON.

```Python
import requests, sys
import json
response = requests.get("<https://itunes.apple.com/search?entity=song(...)>")
print(response.json()) # Print respose formatted for json (as a py dictionary))
print(json.dumps(response.json(), indent=2)) # Print response indented to read easily.
o = response.json()
for result in o["results"]:
	print(result["trackName"]) # Print all songs in the response
```

### Create Custom Library

```Python
# LIBRARY: lib.py file
def main():
	hello("world")
def hello(name):
	print(f"hello, {name}")
# Only call main if ran from the command line. Not as a library.
if __name__ == "__main__":
	main()
```

```Python
# MAIN CODE: main.py file
from lib import hello
hello("trude")
```

## Functions

```Python
def hello(name):
	print(f'Hello {name}')
hello("trude")
```

### Default Values

```Python
def hello(to='world'):
	print("hello,", to)
hello() # hello, world
hello('trude') # hello, trude
```

### Main Function

```Python
def main():
	# main code ...
# other fucntions ...
main()
```

### Generator Function (Yield)

Return one value at a time, without ending the function, and then loop again.  
Useful when working with a large amount of logic that takes a long time to execute.

```Python
def sheep(n):
	for i in range(n):
		yield "SHEEP" * i
```

`yield` returns an iterator that allows the program to handle one element at a time and not lose track.

### Command Line Arguments

```Python
from sys import argv
if len(argv) == 2:
	print(f"Hello, {argv[1]}")
else:
	print("Hello World.")
# argv[0] is the program name
# List all arguments
for arg in argv:
	print(arg)
```

#### Flags

To support commands like: `python file.py -n 2`

```Python
import argparse
parser = argparse.ArgumentParser(description="Meow like a cat")
parser.add_argument("-n", default=1, type=int, help="number of times to meow")
args = parser.parse_args()
for _ in range(args.n): # .n is the -n flag
	print("meow")
```

#### Allow Function to Take Any Number of Arguments (Unpack)

```Python
def f(*args, **kwargs):
	print("Positional: ", args)
	print("Named: ", kwargs)
f(100, 50, 25) # Positional: (100, 50, 25)
f(100, 50, 25, 5) # Positional: (100, 50, 25, 5)
f(gold=10, silver=20, copper=1) # Named: {'gold': 10, 'silver'20 ...}
```

### Pass Functions as Arguments

```Python
def get_name(student):
	return student["name"] # Sort by student name
for student in sorted(students, key=get_name):
	...
```

#### Lambda Function

Pass a function without having to give it a name.

```Python
for student in sorted(students, key=lambda student: student["name"]):
	...
```

## Exceptions

```Python
# Int
try: # Try to execute
	x = int(input("What's your number?"))
except ValueError: # If error is ValueError
	print("Not a number.")
except: # If error
	print("Unknown Error.")
else: # If success
	print(f"Your number+1 is {x + 1}")
```

```Python
try:
	...
except:
	pass # Catch the error, but then ignore it.
```

## Operators

### Comparison Operators

- `<`
- `>`
- `≤`
- `≥`
- `≠`
- `==`

### Logic Operators

- `and`
- `or`

```Python
s = input("Do you agree?")
if s == 'y' or s == 'Y' or s == "Yes" or s == "yes":
	(...)
# This can be improved:
s = input("Do you agree?").lower()
if s in ['y', 'yes']:
	(...)
```

## Scope

1. Program
2. Function (Variables in loops and IFs are global to the function)

## Exit with Error Codes

```Python
from sys import exit
exit(1) # Exit with error code 1
exit("Failed for some reason")
```

## Unit Tests

Helper code to test the main code and (hopefully) catch bugs before they can reach production.

### Manual Tests

**Main code**

```Python
def main():
	x = int(input("X value: "))
	print(f"X^2 is {square(x)}")
def square(n):
	return(n * n)
if __name__ = "__main__":
	main()
```

**Test code (Using IF)**

```Python
from file import square
def main():
	test_square()
def test_square():
	if square(2) != 4:
		print("2 squared was not 4.")
	if square(3) != 9:
		print("3 squared was not 9.")
if __name__ == "__main__":
	main()
```

**Improved test code (Using assert)**

```Python
from file import square
def main():
	test_square()
def test_square():
	try:
		assert square(2) == 4
	except AssertionError:
		print("2 squared was not 4.")
	try:
		assert square(3) == 9
	except AssertionError:
		print("3 squared was not 9.")
if __name__ == "__main__":
	main()
```

### **Pytest**

`pytest` is an external program that simplifies python's unit testing.

```Python
import pytest
from file import square
def test_square():
	assert square(2) == 4
	assert square(3) == 9
	assert square(-2) == 4
	assert square(-3) == 9
	assert square(0) == 0
def test_str(): # Verify that an error will happen for a str
	with pytest.raises(TypeError):
		square("cat")
```

Test with `pytest`:

```Shell
pytest test_file.py
```

> A function with side effects (doesn't return a value) can't be tested.

## File I/O

### Write to a File (Replace)

```Python
name = "trude"
file = open("name.txt", "w")
file.write(name)
file.close()
```

### Append to a File (Add)

```Python
name = "trude"
file = open("name.txt", "a")
file.write(name)
file.close()
```

### Open a File and close Automatically

```Python
name = "trude"
with open("name.txt", "a") as file:
	file.write(name) \\#File is only open in this scope
```

### Read a File

`"r"` is optional when reading.

```Python
with open("name.txt", "r") as file:
	lines = file.readlines() # Return all lines as a list
for line in lines:
	print(line.rstrip()) # Print all lines and remove \\n separating each line
```

#### Better Code for Reading Every line

```Python
with open("name.txt") as file:
	for line in file:
		print(line.rstrip())
```

#### Sort File Lines

```Python
names = []
with open("name.txt") as file:
	for line in file:
		names.append(line.rstrip())
for name in sorted(names):
	print(f"Hello, {name}")
```

### CSV Files

#### Read

**Python**

```Python
import csv
students = []
with open("students.csv") as file:
	reader = csv.reader(file)
	for name, home in reader:
		students.append({"name": name, "home": home})
for student in students:
	print(f"{student['name]"} is from {student['home']}")
```

**CSV File**

```Plain
trude, myHome
maxs, hisHome
jcionx, anotherHome
```

#### Dict Reader

Ignores column order and doesn't break the program if new columns are added. It is recommended to **always** use `**DictReader**` **instead of** `**Reader**`**.**  
**Python**

```Python
import csv
students = []
with open("students.csv") as file:
	reader = csv.DictReader(file)
	for row in reader:
		students.append({"name": row["name"], "home": row["home"]})
for student in students:
	print(f"{student['name]"} is from {student['home']}")
```

**CSV File**

```Plain
name, home
trude, myHome
maxs, hisHome
jcionx, anotherHome
```

#### Write

```Python
import csv
name = 'trude'
home = 'myHome'
with open("students.csv", "a") as file:
	writer = csv.writer(file)
	writer.writerow([name, home])
```

#### Dict Writer

```Python
import csv
name = 'trude'
home = 'myHome'
with open("students.csv", "a") as file:
	writer = csv.DictWriter(file, fieldnames=["name", "home"])
	writer.writerow({"name": name, "home": home})
```

### Other Formats

- TXT
- CSV
- JSON
- BINARY
    - Images (PIL library)

#### Animated GIF Program

```Python
from PIL import Image
items = ["image1.png", "image2.png"]
images = []
for item in items:
	image = Image.open(item)
	images.append(image)
images[0].save(
	"images.gif", save_all=True, append_images=[images[1]], duration=200, loop=0
)
```

## Regular Expressions (regex)

Define patterns for comparisons.

```Python
import re
email = input("Email Address: ").strip()
if re.search(r"^.+@.+\\.edu$", email, re.IGNORECASE):
	print("Email is valid.")
else:
	print("Email is not valid.")
```

### Regular Expression Symbols

- `.` any character except a newline
- 0 or more repetitions
- `+` 1 or more repetitions
- `?` 0 or 1 repetition
- `\\` escape character
- `{m}` m repetitions
- `{m,n}` m-n repetitions (range)
- `^` match the start of the string
- `$` match the end of the string
- `[]` set of characters
- `[^]` complementing the set
- `\\d` decimal digit
- `\\D` not a decimal digit
- `\\s` whitespace characters
- `\\S` not a whitespace character
- `\\w` word character, numbers and underscore
- `\\W` not a word character
- `A|B` either A or B
- `(…)` a group
- `(?:…)` non-capturing version  
Always use `r"string"` (raw string) so python won't interpret `\\` as a special character.

### RE Lib Flags

- `re.IGNORECASE`
- `re.MULTILINE`
- `re.DOTALL`

### Examples

#### Regular Expression for Email Validation

```Python
import re
regex = re.compile(r"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$")
def isValid(email):
    if re.fullmatch(regex, email):
      print("Valid email")
    else:
      print("Invalid email")
```

[More regular expressions](https://www.labnol.org/internet/regular-expressions-forms/28380/)  
`compile` caches the query to make new lookups faster.  
When a regular expression becomes too complex, the rule of thumb is to **use a library instead**.

## Object-Oriented Programming: OOP

### Tuple

An immutable (constant) list.

```Python
tuple = (val1, val2)
```

#### Return More than One Value from a Function

```Python
def get_student():
	n = input("Name: ")
	h = input("House: ")
	return n, h
name, house = get_student()
print(name)
student = get_student()
print(student[0])
```

To make the values mutable, return a list instead.

```Python
return [item1, item2]
```

### Class

Classes are 'blueprints' for data; They define custom data types.  
For example, you can use a class to represent a real-world entity.

#### Basic Class

```Python
class Student:
	... # '...' is a valid placeholder
student = Student  # Create an object from the Student class
student.name = "trude" # Add a new instance variable
student.house = "myHome"
print(student.name) # 'trude'
```

#### Attributes

```Python
class Student:
	def __init__(self, name, house): # Initialization function
		self.name = name
		self.house = house
name = 'trude'
house = 'myHome'
student = Student(name, house)
```

- `self` is the object created from the class definition.
- When an object is created, the `__init__` function is executed.

#### Attributes Error Handling

```Python
class Student:
	def __init__(self, name="Unknown", house): # Initialization function
		if not name:
			raise ValueError("Missing name") # Create and output a custom error
		if house not in ["myHome", "yourHome", "hisHome"]:
			raise ValueError("Invalid house")
		self.name = name
		self.house = house
```

#### Change what Happens when an Object is Printed

```Python
class Student:
	def __init__(self, name, house): # Initialization function
		self.name = name
		self.house = house
	def __str__(self):
		return f"A Student - {self.name}"
student = Student("trude", "myHome")
print(student) # "A Student - trude"
```

#### Methods

```Python
class Mage:
	def __init__(self, name, clan, element): # Initialization function
		self.name = name
		self.clan = clan
		self.element = element
	def castElement(self):
		match self.element:
			case "Fire":
				return "FIRE SPELL"
			case "Water":
				return "Water SPELL"
			case "Earth":
				return "Earth SPELL"
			case "Air":
				return "AIR SPELL"
			case _:
				return "NO SPELL"
mage = Mage("trude", "magicINC", "Fire")
print(mage.castElement())
```

#### Properties

Dot notation (`student.name = ""`) can bypass any data validation. To avoid this, make attributes constant.  
A constant attribute is referred to as a property.

```Python
class Mage:
	def __init__(self, name, clan, element): # Initialization function
		self.name = name
		self.clan = clan
		self.element = element
	# Getter (function to get house attribute)
	@property # Indicates that this is a getter
	def clan(self):
		return self._clan # Add _ to avoid name collision.
	# Setter (function to set a value)
	@clan.setter # Indicates that this is a setter
	def clan(self, clan):
		if clan not in ["magicINC", "spellInator", "glyphGens"]:
			raise ValueError("Invalid Clan")
		self._clan = clan
mage = Mage("trude", "spellInator", "Fire")
mage.clan = "not-a-clan"
```

#### Example

```Python
class Mage:
	...
def main():
	mage = get_mage()
	print(f"{mage.name} from {mage.clan}")
def get_mage():
	mage = Mage()
	mage.name = "trude"
	mage.clan = "spellInator"
	return mage
if __name__ == "__main__":
	main()
```

#### Class Methods

A method that relates to the class itself, and doesn't depend on the objects individually.

```Python
import random
class Hat:
	def __init__(self):
		self.clans = ["magicINC", "spellInator", "glyphGen"]
	def sort(self, name):
		print(name "is in", random.choice(self.clans))

hat = Hat()
hat.sort("Trude")
```

#### Class Variables

- `self.var` A variable unique for each object
- `var` A variable shared with all objects

```Python
class Hat:
	clans = ["magicINC", "spellInator", "glyphGen"]
	@classmethod
	def sort(cls, name):
		print(name "is in", random.choice(cls.clans))
Hat.sort("Trude")
```

A class method is a method global to the class, instead of created for each object. It relates to the data type (class) itself.

#### Example

```Python
class Mage:
	...
	@classmethod
	def get(cls):
		name = input("Name: ")
		clan = input("Clan: ")
		return cls(name, clan) # name and clans are returned to the class as self.name and self.clan.
	...
def main():
	mage = Mage.get()
	print(mage)
if __name__ == "__main__":
	main()
```

### Inheritance

Inherit code from a parent class to avoid code duplication.

```Python
class Person:
	def __init__(self, name)
		if not name:
			raise ValueError("Missing name")
		self.name = name
class Student(Person): # Pass Person to Student
	def __init__(self, name, house):
		super().__init__(name) # Execute Person's __init__ method.
		self.house = house
	...
class Professor(Person):
	def __init__(self, name, subject):
		super().__init__(name)
		self.subject = subject
	...
person = Person("Random Guy")
student = Student("Trude", "myHome")
professor = Professor("Someone", "Math")
```

### Operator Overloading

Customize what an operator does with a Class.
- `__add__` Behavior of '+' operator.

```Python
class Vault:
	def __init__(self, gold=0, silver=0, copper=0):
		self.gold = gold
		self.silver = silver
		self.copper = copper
	def __str__(self):
		return f"{self.gold} Gold, {self.silver} Silver, {self.copper} Copper."
	def __add__(self, other):
		gold = self.gold + other.gold
		silver = self.silver + other.silver
		copper = self.copper + other.copper
		return Vault(gold, silver, copper)
trude = Vault(100, 50, 25)
jcionx = Vault(25, 50, 100)
print(trude) # 100 Gold, 50 Silver, 25 Copper
print(jcionx) # 25b Gold, 50 Silver, 100 Copper
total = trude + jcionx
print(total) # 125b Gold, 100 Silver, 125 Copper
```
