Learning Python
Writing Comments
””” Some comments being written in python “””
’’’ We can use single quotes as well ‘’’
Printing
1
print("Hello", "World", sep="-", end="!")
Range
1
2
3
4
range(5)
range(5,10)
range(5,10,3)
list(range(5))
Execution Control
1
2
3
4
5
6
if i == 40:
print("i am 40")
elif i > 40:
print("i am greater than 40")
else:
print("i am less than 40")
for loops
1
2
3
4
5
6
7
for i in range(10):
if i == 8:
break
elif i == 7:
continue
else
print(i)
while loop
1
2
3
4
5
6
7
8
while loop can be used with else
count = 0
while count < 10:
print(count)
count = count + 1
else:
print("Out of loop")
Handling Exceptions
Use try-except block in python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
thinkers = [
'Plato',
'PlayDo',
'Gumby'
]
while True:
try:
thinker = thinkers.pop()
print(thinker)
except IndexError as e:
print("We tried to pop too many thinkers")
print(e)
break
A bit on classes and objects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class FancyCar:
# class variable
wheels = f
# instance method
def driveFast(self):
print("Driving so fast")
# class methods, use @classmethod with cls keyword
@classmethod
def bla(cls)
print("I am class method")
# only inside class namespace
@staticmethod
def add(a, b)
print('static method')
obj = FancyCar()
obj.instance_method()
obj = FancyCar()
obj.instance_method()
FancyCar.static_method()
obj.static_method()
Some useful operations around sequences
1
2
3
4
5
6
7
8
9
10
11
12
2 in [1,2,3]
'a' not in 'cat'
10 in range(12)
10 not in range(2, 4)
my_sequence = "Bill Cheatham"
my_sequence.index('C')
my_sequence = [1,2,3,4,5,6]
my_sequence[start:stop:step] # if nothing is passed then the defaults are start(0), stop(last), step(1)
# some array methods
.append, .remove, .pop, .insert
# some string methods
.capitalize, .upper, .swapcase, .startswith('s'), .endswith('k'), .isalnum(), isnumberic(), .isalpha()
Dict
1
2
3
4
5
6
7
map = {'key': 'value', 'abba': 'kabba'}
# adding element
map['something'] = 'nothing'
# looping over items
for k, v in map.items():
print(k, v)
Functions
1
2
3
4
5
6
7
8
9
10
def bla(a, b)
def bla(a=4, b=9)
def kla:
pass # implement later
# function as objects
def bla(a)
def kla(b)
for i in [bla, kla]
i(4)
Anonymous Fns
lambda param1, param2, … : expression mul = lambda x, y, z: x * y * z
regex in python
import re
r is used for literal values, instead of new line it would look for exact \n(newline)
reg = r”\n”
reg = r’\b[a-zA-Z]+\Sl.p[a-z]?[0-9]{6}.\b’ \b → word boundary [a-zA-Z]+ → one or more letters \S -> any single character that is not a whitespace character . → any single character [0-9]{6} → exactly 6 digits [0-9]{1,4} -> matches 1 to 4 digits . → any characters after that ? → optional (0 or 1 occurrence)
reg = r’(\w+)\@(\w+).(\w+)’ bla@bla.abba
matched = re.findall(r’abba’, variable) matched [‘abba’, ‘abba’…..]
lazy evaluation
Lazy evaluation is the idea that, especially when dealing with large amounts of data, you do not want process all of the data before using the results. Y ou have already seen this with the range type, where the memory footprint is the same, even for one representing a large group of numbers.
1
2
3
4
5
6
7
8
9
10
11
def count():
n = 0
while True:
n += 1
yield n
counter = count()
next(counter)
>> 1
next(counter)
>> 2
1
2
3
4
5
6
nums_list = [x for x in range(100)] # normal
nums_gen = (x for x in range(100)) # lazy
import sys
sys.getsizeof(nums_list)
sys.getsizeof(nums_gen)
opening files
with open(‘f.txt’, ‘r/w/rb….’) as f: k1 = f.read() k2 = f.readlines()
import pathlib path = pathlib.Path(“/Users/kbehrman……../bla.py”) path.read_text()
for binary data path.read_bytes()
working with json files import json
with open(‘service.json’, ‘r’) as f: p = json.load(f) p[‘a’] = 90909 print(p)
Read line by line — use this for logs (memory efficient, file never fully loaded)
with open(“app.log”) as f: for line in f: print(line.strip()) # strip() removes the trailing \n
context managers
You already used a context manager here:
1
2
3
with open("app.log") as f:
for line in f:
print(line.strip())
Motivation:
- Some resources must be cleaned up no matter what (files, DB connections, locks, temp files).
- Without
with, you can forget to close/release on error paths. - Context managers keep setup + cleanup together and safer.
Under the hood, with calls:
__enter__()at the start__exit__()at the end (even if exception happens)
Custom implementation using a class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ManagedFile:
def __init__(self, path, mode):
self.path = path
self.mode = mode
self.f = None
def __enter__(self):
self.f = open(self.path, self.mode)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
if self.f:
self.f.close()
# False means: do not swallow exceptions
return False
with ManagedFile("notes.txt", "w") as f:
f.write("hello from custom context manager")
Custom implementation using contextlib.contextmanager:
1
2
3
4
5
6
7
8
9
10
11
12
from contextlib import contextmanager
@contextmanager
def managed_file(path, mode):
f = open(path, mode)
try:
yield f # value after "as"
finally:
f.close() # always runs
with managed_file("notes.txt", "a") as f:
f.write("\nline 2")
the os module
1
2
3
4
5
6
7
os.listdir('.')
os.rename('_crud_handler', 'crud_handler')
os.chmod(fname, '0o777')
os.mkdir('/tmp/holding')
os.mkdirs('/tmp/holding',...)
os.remove('f')
os.rmdir(dir)
1
2
3
4
5
6
import os
# get full path
cur_dir = os.getcwd()
os.path.split(cur_dir)
os.path.dirname(cur_dir)
os.path.basename(cur_dir)
spawn processes with the subprocess module
1
2
3
4
5
import subprocess
k = subprocess.run(['ls', '-l'], capture_output=True, check=True, text=True, timeout=10)
# capture_output holds the data instead of directly throwing to stdout, can be accessed by k.stdout
# check=True raises exception if the process throws any exception
k.stdout
capturing arguments
1
2
3
4
5
6
7
8
9
10
11
# doing this makes sure that func does not get executed when you are importing code
# only gets executed when you do something like python3 file.py
if __name__ == '__main__':
func()
import sys
# if you wish to capture the args passed
sys.argv[0]
sys.argv[1]
def bla(log_path: str) vs def bla(log_path = str)
log_path: str is a type hint inside a function signature that labels the expected data type (it says “this should be text”). It’s not enforced.
log_path = str is an assignment that makes the variable equal to the actual Python string class itself (not a specific word or path).
TL;DR: Use : to describe what a variable should be, and = to give it a specific value.
fn signatures
Gives info on how the return type should look like, not enforced
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def get_user(id: int) -> str:
Return: "Alice"
def get_scores() -> list[int]:
Return: [88, 92, 100]
def get_counts() -> dict[str, int]:
Return: {"apple": 5, "orange": 2}
def get_grid() -> list[list[int]]:
Return: [[1, 0], [0, 1]]
def get_cube() -> list[list[list[int]]]:
Return: [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
def get_data() -> dict[str, list[int]]:
Return: {"ids": [101, 102], "codes": [200, 404]}
def find_name() -> str | None:
Return: "Bob" or None