None
is a Singleton:
The keyword is
checks whether two references / names refer to the same object.
a = [1, 2]
b = a
x = [1, 2]
a == b # True
a is b # True
a == x # True
a is x # False
As None
is a singleton we can check for it via is None
:
if a is None:
print("a is None")
True
or False
a = True
if a:
print('hello')
Internally False
behaves almost like 0
and True
behaves almost like 1
False + True # 1
10 // 3
10 % 3
2 ** 3
to help us read long numbers:
earth_circumference = 40075017
earth_circumference = 40_075_017
integers of arbitrary size
Other numeral systems:
a = 42 # decimal
b = 0b101010 # binary
c = 0o52 # octal
d = 0x2a # hexadecimal
e = int('101010', 2)
64 bit float
a = 2.3
b = .2
c = 6e23
d = float('nan')
e = float('inf')
rounding errors: some numbers cannot be represented as floating point numbers, they will always be approximations
examples in the decimal system: 1/3, 1/7, π
examples in the binary system (i.e. floats): 1/10, 1/5, 1/3, π
example: π + π evaluates to 6.2
when using decimal numbers with a precision of 2 (a more exact result would be 6.3
)
example: 0.1 + 0.2
evaluates to ~ 0.30000000000000004
when using 64 bit floats
0.1 + 0.2 == 0.3
# False
import math
math.isclose(0.1 + 0.2, 0.3)
# True
IEEE 754: standardized floating point arithmetic
Python mostly acts as defined in the standard
deviation from the standard: in some cases Python will throw exceptions where the standard produces a result - e.g. 1.0/0.0
Special numbers in IEEE 754:
inf
and -inf
(infinite values)nan
(not-a-number: undefined / unknown value)a = 2 + 3j
For binary operators there are so-called augmented assignments:
a = a + 1
short form (augmented assignment):
a += 1
other operations: -=
, *=
, ...
Unicode: catalog of over 100,000 international characters, each with a unique identifying name and number (usually written in hexadecimal)
examples:
Character encoding = mapping of characters to bit sequences
A character encoding is necessary in order to write text to disk or transfer it over the network
Examples in ASCII / Latin1 / UTF-8:
!
↔ 00100001
A
↔ 01000001
a
↔ 01100001
Examples in Latin1:
Ä
↔ 11000100
Examples in UTF-8:
Ä
↔ 11000011 10100100
🙂
↔ 11110000 10011111 10011001 10000010
In many areas (in particular on the web) UTF-8 has become the standard text encoding
In UTF-8 the first 128 Unicode characters can be encoded in just 8 bit
All other characters need either 16, 24 or 32 bit
UTF-32 encodes the Unicode code points directly
Depending on the area of application the byte order may differ (big endian or little endian)
example:
🙂 (U+1F642) ↔ 00 01 F6 42
(big endian) or 42 F6 01 00
(little endian)
Line breaks can be represented by the characters LF
(line feed, U+000A
) and / or CR
(carriage return, U+000D
)
LF
: Standard on Linux, MacOSCRLF
: Standard on Windows, in network protocols like HTTPIn string literals LF
is often represented by \n
and CR
is represented by \r
In Python 3 strings are sequences of Unicode characters
Examples:
a = "test"
b = 'test'
a = """this
is a multi-line
string literal.
"""
Some characters may be entered via so-called escape sequences:
a = "He said:\n\"Hi!\""
\'
→ '
\"
→ "
\\
→ \
\n
→ Line Feed (line separator on Unix)\r\n
→ Carriage Return + Line Feed (line separator on Windows)\t
→ Tab\xHH
or \uHHHH
or \UHHHHHHHH
→ Unicode-Codepoint (hexadecimal)If we don't need to use any escape sequences in a string:
path = r"C:\documents\foo\news.txt"
This can be useful when writing Windows paths and regular expressions
.lower()
.upper()
.startswith(...)
.endswith(".txt")
.center(10)
.ljust(10)
.rjust(10)
.strip()
.split(' ')
.splitlines()
.join()
sources:
input:
Rodorigo. Neuer tell me, I take it much vnkindly
That thou (Iago) who hast had my purse,
As if y strings were thine, should'st know of this
target:
Rodorigo. Neuer tell me, I take it much vnkindly 1
That thou (Iago) who hast had my purse, 2
As if y strings were thine, should'st know of this 3
tasks:
String formatting = placing values in strings
Methods:
greeting = "Hello, " + name + "!"
greeting = f"Hello, {name}!"
city = 'Vienna'
temperature = 23.7
# rather obsolete
'weather in %s: %f°C' % (city, temperature)
'weather in {0}: {1}°C'.format(city, temperature)
'weather in {}: {}°C'.format(city, temperature)
'weather in {c}: {t}°C'.format(c=city, t=temperature)
f'weather in {city}: {temperature}°C'
.4f
: four decimal places after the decimal point.4g
: four decimal placesprint(f"Pi is {math.pi:.4f}")
# Pi is 3.1416
print(f"Pi is {math.pi:.4g}")
# Pi is 3.142
>8
: right-aligned (total width 8)^8
: centered<8
: left-alignedprint(f"{first_name:>8}")
print(f"{last_name:>8}")
John
Doe
combination:
print(f"{menu_item:<12} {price:>5.2}$")
Burger 11.90$
Salad 8.90$
Fries 3.90$
further options:
Lists are mutable sequences of objects; they are usually used to store homogenous entries of the same type and structure
primes = [2, 3, 5, 7, 11]
users = ["Alice", "Bob", "Charlie"]
The following operations will also work on other sequences - e.g. tuples, strings or bytes
users[2]
users[2:4]
users + users
3 * users
len(users)
for user in users:
if 'Tim' in users:
Lists can be mutated directly (while strings and tuples can't be):
users.append("Dan")
users.insert(2, "Max")
users.pop()
users.pop(2)
sorting by default order (alphabetically for strings)
l.sort()
sorting by custom order:
l.sort(key=len)
def count_a(s):
return s.count("a")
l.sort(key=count_a)
Entries are separated by commas, usually surrounded by round brackets.
empty_tuple = ()
single_value = ('Thomas', )
single_value = 'Thomas',
two_values = ('Thomas', 'Smith')
two_values = 'Thomas', 'Smith'
time = (23, 45, 0)
hour, minute, second = time
swapping variables:
a, b = b, a
when reading from storage media or reading network responses we may have to deal with bytes: sequences of integers in the range of 0 to 255 (8 bits)
bytes may represent images, text, data, ...
bytes are often written in hexadecimal notation instead of decimal:
hexadecimal literals in Python:
0x1
0x9
0xa
0xf
0x10
0x11
0x1f
0x20
creating bytes from a list of numbers:
a = bytes([0, 64, 112, 160, 255])
b = bytes([0, 0x40, 0x70, 0xa0, 0xff])
creating bytes from a byte string literal:
c = b"\x00\x40\x70\xa0\xff"
ASCII values can be included directly (\x40
= "@", \x70
= "p"):
d = b"\x00@p\xa0\xff"
Standard representation in Python:
print(bytes([0x00, 0x40, 0x70, 0xa0, 0xff]))
b'\x00@p\xa0\xff'
Where possible, bytes will be represented by ASCII characters; otherwise their hex code will be shown
Bytes will often hold encoded text
If we know the encoding we can convert between bytes and strings:
'ä'.encode('utf-8')
# b'\xc3\xa4'
b'\xc3\xa4'.decode('utf-8')
# 'ä'
Python sequences consist of other Python objects
examples:
s[2]
s[2:4]
s + t
3 * s
len(s)
for el in s:
if el in s:
Accessing elements
users = ['mike', 'tim', 'theresa']
users[0] # 'mike'
users[-1] # 'theresa'
Changing elements
(if the sequence is mutable)
users = ['mike', 'tim', 'theresa']
users[0] = 'molly'
Accessing multiple elements
users = ['mike', 'tim', 'theresa']
users[0:2] # ['mike', 'tim']
Concatenation
users = ['mike', 'tim', 'theresa']
new_users = users + ['tina', 'michelle']
Repetition
users = ['mike', 'tim', 'theresa']
new_users = users * 3
Length
users = ['mike', 'tim', 'theresa']
print(len(users))
for loop
users = ['mike', 'tim', 'theresa']
for user in users:
print(user.upper())
Dictionaries are mappings of keys to values
person = {
"first_name": "John",
"last_name": "Doe",
"nationality": "Canada",
"birth_year": 1980
}
Accessing entries
person["first_name"] # "John"
Iterating over dictionaries
for entry in person:
print(entry)
This will yield the keys: "first_name"
, "last_name"
, "nationality"
, "birth_year"
Since Python 3.7 the keys will always remain in insertion order
Iterating over key-value-pairs:
for key, value in person.items():
print(f'{key}, {value}')
d = {0: 'zero', 1: 'one', 2: 'two'}
d[2]
d[2] = 'TWO'
d[3] # KeyError
d.get(3) # None
d.setdefault(2, 'n')
d.setdefault(3, 'n')
d.keys()
d.items()
d1.update(d2)
Any immutable object can act as a dictionary key. The most common types of keys are strings.
a = 20
a.to_bytes(1, "big")
"hello".upper()
message = "hello"
type(message)
isinstance(message, str)
Classes may represent various things, e.g.:
The definition of a class usually encompasses:
example: class TextIOWrapper
can represent a text file (is created when calling open()
)
attributes:
methods:
example: class BankAccount
class MyClass():
# the method __init__ initializes the object
def __init__(self):
# inside any method, self will refer
# to the current instance of the class
self.message = "hello"
instance = MyClass()
instance.message # "hello"
Attributes and methods that should not be used from the outside are prefixed with _
We're all consenting adults here: https://mail.python.org/pipermail/tutor/2003-October/025932.html
a = Length(2.54, "cm")
b = Length(3, "in")
a.unit
a.value
tdl = TodoList("groceries")
tdl.add("milk")
tdl.add("bread")
print(tdl.todos)
tdl.todos[0].toggle()
tdl.stats() # {open: 1, completed: 1}
often we can use some class(es) as the basis for another class
e.g.:
User
class as the basis of the AdminUser
classTicTacToeGame
as the basis of TicTacToeGameGUI
inheritance: an AdminUser
is a User
composition: TicTacToeGameGUI
could use TicTacToeGame
in the background
common mantra: composition over inheritance: don't overuse inheritance
inheritance:
class User():
...
class AdminUser(User):
...
the AdminUser
class automatically inherits all existing methods from the User
class
composition:
class TicTacToeGame():
...
class TicTacToeGameGUI():
def __init__(self):
self.game = TicTacToeGame()
example of inheritance - database model in Django:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
standard styleguide for Python code
official document: https://www.python.org/dev/peps/pep-0008/
cheatsheet: https://gist.github.com/RichardBronosky/454964087739a449da04
In VS Code config: "python.formatting.provider": "black"
input:
a='Hello'; b="Have you read \"1984\"?"
c=a[0+1:3]
output via black:
a = "Hello"
b = 'Have you read "1984"?'
c = a[0 + 1 : 3]
Quotes from the zen of Python (full text via import this
):
Docstrings = Strings that describe functions / classes / modules in more detail
comments in a function: help programmers who develop that function
docstring of a function: help programmers who use that function
Example:
def fib(n):
"""Compute the n-th fibonacci number.
n must be a nonnegative integer
"""
...
help(fib)
help(round)
Breakpoints can be set to pause execution at a certain point.
Possibilities to set breakpoints:
breakpoint()
(since Python 3.7)Executing in VS Code:
Symbol "Run and Debug" in the left toolbar
select "create a launch.json file" - "Python" - "Python File"
via Debug - Start Debugging or F5.
Continuing manually:
c
for continue in the Python debuggerq
for quit in the Python debuggerContinuing manually:
n
for next in the Python debuggers
for step in the Python debuggerr
for return in the Python debuggerExamining values in VS Code:
Printing values in the Python debugger via p
:
p mylist
p mylist[0]
if
while
for ... in ...
for ... in range()
try ... except ...
From a previous example:
if age_seconds < 1000000000:
print("You are less than 1 billion seconds old")
else:
print("You are older than 1 billion seconds")
When using conditions for if
/ while
we usually use expressions that evaluate to boolean values.
However, we can also use other types:
a = 0
if a: ...
name = input("enter your name")
if name: ...
products = []
if products: ...
These types are converted to boolean values before being used as criteria for the if condition.
Any value may be used as a condition in Python. Most values will be "truthy".
Only these values are considered "falsy" - calling bool(...)
will return False
:
False
0
, 0.0
None
""
, []
, ()
, {}
)datetime.time(0, 0, 0)
)Not "pythonic":
name = input("Enter your name:")
if name != "":
...
"pythonic" version:
name = input("Enter your name:")
if name:
...
checking if age
lies in the range of 13-19:
13 <= age and age <= 19
short version:
13 <= age <= 19
checking if a
and b
are both 0
(short version):
a == b == 0
An expression that evaluates to one of two possibilities based on a boolean criterion
size = 'small' if length < 100 else 'big'
in other languages this could be written as:
// JavaScript
size = length < 100 ? 'small' : 'big';
Recap: tuple unpacking
time = (23, 45, 0)
hour, minute, second = time
Enumerating list items:
l = ['Alice', 'Bob', 'Charlie']
for i, name in enumerate(l):
print(f'{i}: {name}')
Enumerate returns a data structure that behaves like this list:
[(0, 'Alice'), (1, 'Bob'), (2, 'Charlie')]
Listing directory contents (including subfolders) via os.walk
:
import os
for directory, dirs, files in os.walk("C:\\"):
print(f"{directory} {files}")
C:\ []
C:\PerfLogs []
C:\Program Files []
C:\ProgramData []
...
keyword continue
: similar to break, but only skips the rest of the current iteration
example:
for name in os.listdir("."):
if not name.endswith(".txt"):
# skip .txt files
continue
# process other files here
List comprehensions enable the creation of lists based on existing lists
In other programming languages this is often done via map
and filter
Transforming each entry:
names = ["Alice", "Bob", "Charlie"]
uppercase_names = [name.upper() for name in names]
result:
["ALICE", "BOB", "CHARLIE"]
Filtering:
amounts = [10, -7, 8, 19, -2]
positive_amounts = [amount for amount in amounts if amount > 0]
result:
[10, 8, 19]
Generic syntax:
new_list = [new_entry for entry in old_list]
new_list = [new_entry for entry in old_list if condition]
colors = {
'red': '#ff0000',
'green': '#008000',
'blue': '#0000ff',
}
m_colors = { color: colors[color][1:] for color in colors}
or
m_colors = {
name: value[1:] for name, value in colors.items()
}
Exercise: try and trigger all of the above exceptions
age_str = input("Enter your age")
try:
age = int(age_str)
except ValueError:
print("Could not parse input as number")
age_str = input("Enter your age")
try:
age = int(age_str)
except ValueError as e:
print("Could not parse input as number")
print(e)
print(e.args)
Catching multiple types of exceptions:
try:
file = open("log.txt", encoding="utf-8")
except FileNotFoundError:
print("could not find log file")
except PermissionError:
print("reading of file is not permitted")
except Exception:
print("error when reading file")
Using finally
:
try:
file = open("log.txt", "w", encoding="utf-8")
file.write("abc")
file.write("def")
except IOError:
print("Error when writing to file")
finally:
file.close()
Using else
:
try:
file = open("log.txt", "w", encoding="utf-8")
except IOError:
print("could not open file")
else:
# no errors expected here
file.write("abc")
file.write("def")
file.close()
LBYL: Look before you leap
EAFP: It's easier to ask for forgiveness than permission
(example: parsing numbers)
raise ValueError('test')
try:
...
except ClientError as e
if "DryRunOperation" not in str(e):
raise
We can define custom exceptions as subclasses of other exception classes:
class MoneyParseException(Exception):
pass
raise MoneyParseException()
Module as a directory:
- foo/
- __init__.py
# __init__.py
a = 1
b = 2
Module as a directory with separated defintions:
- foo/
- __init__.py
- _a_mod.py
- _b_mod.py
# __init__.py
from foo._a_mod import a
from foo._b_mod import b
To see all search paths for imports:
import sys
print(sys.path)
Imported modules will be saved in a compiled form, making subsequent loading of the modules faster.
Compiled versions will be saved in the folder __pycache__
inside a an imported module, the variable __name__
gives its name
if a Python file was run directly instead of being imported, its __name__
will be "__main__"
if __name__ == "__main__":
print("this file was run directly (and not imported)")
Installation eines Pakets via PIP:
pip install cowsay
Installation einer bestimmten Version:
pip install cowsay==6.1
Installation einer kompatiblen Version (könnte auch Versionen 6.2, 6.3, etc. installieren - falls verfügbar)
pip install cowsay~=6.1
Virtuelle Umgebungen: ermöglichen es, für verschiedene Projekte unterschiedliche Abhängigkeiten und Versionen von Abhängigkeiten zu installieren
Erstellen einer neuen Virtuellen Umgebung (typischer Name: ".venv")
python -m venv .venv
erstellt einen Ordner ".venv/", der die virtuelle Umgebung enthält
Aktivieren einer Virtuellen Umgebung unter Windows:
./.venv/Scripts/activate
Deaktivieren der Umgebung:
deactivate
falls nötig: erlaube die Ausführung lokaler Scrits unter Windows - aus einem Admin-Terminal:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
"alte" Konfigurationsdatei: requirements.txt
"neue" Konfigurationsdatei: pyproject.toml
Beispiel für requirements.txt:
cowsay~=6.1
requests~=2.30
Installation der Abhängigkeiten in requirements.txt:
pip install -r requirements.txt
Beispiel für pyproject.toml:
[project]
name = "my-python-project"
version = "1.0"
dependencies = [
"cowsay~=6.1",
"requests~=2.30",
]
Installation:
pip install .
def foo(*args, **kwargs):
print(args)
print(kwargs)
foo("one", "two", x="hello")
# args: ("one", "two")
# kwargs: {"x": "hello"}
args
will be a tuple, kwargs
will be a dictionary
Task: recreate range()
by using a while loop
numbers = ["one", "two", "three"]
# equivalent:
print(numbers[0], numbers[1], numbers[2])
print(*numbers)
global
/ nonlocal
change the behavior of assignments
Example: rock, paper, scissors
import random
wins = 0
losses = 0
def play_rock_paper_scissors():
player = input("rock, paper or scissors?")
opponent = random.choice(["rock", "paper", "scissors"])
if player == opponent:
pass
elif (
(player == "rock" and opponent == "scissors")
or (player == "paper" and opponent == "rock")
or (player == "scissors" and opponent == "paper")
):
global wins
wins += 1
else:
global losses
losses += 1
while input("play? (y/n)") != "n":
play_rock_paper_scissors()
print(f"won: {wins}, lost: {losses}")
A better alternative to the global
keyword is often to create a class:
import random
class RockPaperScissors():
def __init__(self):
self.wins = 0
self.losses = 0
def play(self):
...
def run(self):
while input("play? (y/n)") != "n":
self.play()
print(f"won: {wins}, lost: {losses}")
Reacap: What will be the value of a
after this code has run?
a = [1, 2, 3]
b = a
b.append(4)
An assignment (e.g. b = a
) assigns a new (additional) name to an object.
The object in the background is the same.
The statement b = a
creates a new reference that refers to the same object.
Operations that create new references:
b = a
)myfunc(a)
- a new internal variable will be created)mylist.append(a)
)Passing an object into a function will create a new reference to that same object (call by sharing).
def foo(a_inner):
print(id(a_inner))
a_outer = []
foo(a_outer)
print(id(a_outer))
pure functions: functions that only interact with their environment by receiving parameters and returning values
side effects: actions of a function that change the environment
common side effects:
side effects that are usually avoided:
advantages of pure functions:
example of a suboptimal function that changes an argument (formats):
def list_files_by_formats(path, formats):
if "jpg" in formats:
formats.append("jpeg")
files = []
for file in os.listdir(path):
for format in formats:
if file.endswith("." + format):
files.append(file)
break
return files
formats = ["jpg", "png"]
print(list_files_by_formats(formats))
print(formats)
# will print: ["jpg", "png", "jpeg"]
more "correct" implementation:
def list_files_by_formats(path, formats):
if "jpg" in formats:
formats = formats.copy()
formats.append("jpeg")
# ...
this would be an anti-pattern (a function that modifies arguments):
mylist = [2, 1, 3]
sort(mylist)
print(mylist)
# [1, 2, 3]
actual possibilites for sorting in Python:
pure function:
print(sorted(mylist))
method that modifies data:
mylist.sort()
print(mylist)
Unexpected behavior in Python when default arguments are mutated:
def list_files_by_formats(path, formats=["gif", "jpg", "png"]):
if "jpg" in formats:
formats.append("jpeg")
# ...
list_files_by_formats(".")
# formats: ["gif", "jpg", "png", "jpeg"]
list_files_by_formats(".")
# formats: ["gif", "jpg", "png", "jpeg", "jpeg"]
(web search: mutable default arguments)
Python 2 vs Python 3
major change in Python 3:
strict separation of text (strings) and binary data (bytes)
in Python 2: data types bytes
, str
and unicode
Python 2:
print "a",
Python 3:
print("a", end="")
Python 2:
10 / 3 # 3
in Python 2: range()
returns a list, xrange()
returns an object that uses less memory
in Python 3: range()
returns an object that uses less memory
in Python 2: input()
will evaluate / execute the input, raw_input()
returns a string
in Python 3: input()
returns a string
Getting some of the behavior of Python 3 in Python 2:
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division
Compatibility layer between Python 2 and Python 3
Enables supporting both Python 2 and Python 3 from the same codebase