This python guide is intended to those who already know python and want a mind refresh or looking for a specific syntax.
-as this guide doesn't elaborate in explaining thing that a programmer would already know -like: variable, function, csv, json-
additionally, this guide is not intended to be a replacement for reading official Python documentation.
guide version: 2.0.0 | python version: 3.10
Part of Version | Explanation |
---|---|
X.x.x | Add new content/section |
x.X.x | Edit a (section content/document structure) |
x.x.X | Fix content |
- Python Guide
- Frameworks
- General Info
- Snippets
- Python Standard Library
- Core Syntax
- the walrus operator(
:=
) - del statement
- pass statement
- scope
- Print Statement
- User Input
- Numbers
- Type Annotation/hinting
- Data types
- Variables
- Type Casting
- String
- lambda expression
- If Condition
- For Loop
- Generator Expressions
- Comprehensive List
- While Loop
- Functions
- Data Structure
- unpack operator
- Exceptions
- release resource
match
statementassert
keyword- class
- the walrus operator(
- doc string
- pydoc
- module
- package management
- linter : check your source code for programmatic and stylistic errors.
- interrupter : execute your source code.
pep8 is a formatting standard guide.
Use 4 spaces per indentation level
Function and class should be surrounded by 2 line breaks
PI = 3.14 # constant | variable can but shouldn't be modified
my_counter = 10 # variable
def my_function(very_long_name_parameter, very_long_name_argument):
return very_long_name_parameter + very_long_name_argument
my_function(1, 2)
# function call with multiple lines
characters_frequency = {"a": 2, "b": 1, "c": 8}
print(sorted(
characters_frequency.items(),
key=lambda frequency_dict: frequency_dict[1],
reverse=True))
class MyClass:
pass
module name example: "my_module.py"
related section: module
python code get converted into platform-independent code(byte code).
Python generates a bytecode which can be Implemented using several programming languages, -some but not all- are in the following table:
name | programming languages |
---|---|
cPython(common Implementation) | C |
Jython | java |
IronPython | C# |
PyPy | subset of python |
how to run python files from command line:
python file.py
for Windowspython3 file.py
for mac / linux (because python 2 exist in them)
type | file extension | contains | opened using |
---|---|---|---|
python file | py | pure python code | pycharm(IDE), visual studio code(editor) |
jupyter notebook | ipynb | cells of python codes with output and markdown | anaconda, colab, visual studio code(editor) |
first in, last out
# declaration
stack = [-1, 0]
# add item at the end
stack.append(1)
# get & remove last item
last_item = stack.pop()
# get last item
last_item = stack[-1]
# check if empty
if not stack:
print("Empty")
related section: list
first in, first out
It's possible to implement queue using a list, but it's not memory efficient. Therefore, deque module from the package collections is used instead.
from collections import deque
# declaration
queue = deque([-1, 0])
# queue
queue.append(1)
queue.append(2)
# dequeue
queue.popleft()
# check if empty
if not queue:
print("Empty")
related section: import module
Arrays are sequence types and behave very much like lists, except that the type of objects stored in them is constrained.
from array import array
type_code = 'i' # type code for int
numbers = array(type_code, [1, 2, 3])
number = 4
numbers.append(number) # array('i', [1, 2, 3, 4])
numbers.append(number) # array('i', [1, 2, 3, 4, 4])
index = 2
numbers.insert(index, number) # array('i', [1, 2, 4, 3, 4, 4])
x = numbers.pop() # x = 4 | numbers = array('i', [1, 2, 4, 3, 4])
numbers.remove(4) # numbers = array('i', [1, 2, 3, 4])
# numbers[1] = 1.0 # TypeError: integer argument expected, got float
related section: import module
related section: tuple
characters = tuple("hello") # ('h', 'e', 'l', 'l', 'o')
numbers = [5, 1, 8, 9, 0, 6, 50, 20]
print(sorted(numbers, reverse=True)) # output: [50, 20, 9, 8, 6, 5, 1, 0] | numbers = [5, 1, 8, 9, 0, 6, 50, 20]
print(sorted(numbers)) # output: [0, 1, 5, 6, 8, 9, 20, 50] | numbers = [5, 1, 8, 9, 0, 6, 50, 20]
related section: lambda expression
characters_frequency = {"a": 2, "b": 1, "c": 8}
print(sorted(
characters_frequency.items(),
key=lambda frequency_dict: frequency_dict[1],
reverse=True))
# using built-in function
from collections import Counter
counter = Counter("hello world")
# Counter = ({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
# using a dictionary
sentence = "hello world"
counter = {}
for character in sentence:
counter[character] = counter.get(character, 0) + 1
# counter = {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}
from pprint import pprint
x = {'w': 2, 'e': 4, 'l': 1, 'c': 1, 'o': 4, 'm': 2, ' ': 4, 't': 1, 'u': 1, 'r': 1, 'n': 1, 'h': 1}
pprint(x, width=1)
from sys import getsizeof
print("'a' takes", getsizeof('a'), "bytes of memory") # 'a' takes 50 bytes of memory
related section: timestamp
# using timeit
from timeit import timeit
code1 = """def xfactor(age):
if age <= 0:
raise ValueError("Age cannot be 0 or less")
return 10 / age
try:
xfactor(0)
except ValueError as error:
pass
"""
code2 = """def xfactor(age):
if age <= 0:
return None
return 10 / age
x = xfactor(0)
if x is None:
pass
"""
print("code1:", timeit(code1, number=10000)) # code1: 0.003757399999999994
print("code2:", timeit(code2, number=10000)) # code2: 0.001474000000000003
related sections: datetime, time deltas
# using Epoch time
import time
# calculate execution time
def send_email():
time.sleep(2)
for i in range(1000):
pass
start = time.time()
send_email()
end = time.time()
duration = end - start
related section: working with file
# path object
from pathlib import Path
path = Path("file.text")
path.read_text() # read file as string
related section: open file
# with open
with open("file.text", 'r') as file:
file.readlines() # list of lines as a strings
related sections: working with file, shutil
# copy file
from pathlib import Path
import shutil
source = Path("text.py")
target = Path("text.txt")
# copy using path object
target.write_text(source.read_text())
# copy using shutil
shutil.copy(source, target)
related section: random number generator, string
Warning: The pseudo-random generators of this module should not be used for security purposes. For security or cryptographic uses, see the secrets module.
# generate a password of 8 letters & numburs
import string
import secrets
alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(8))
related section: module as a script
if __name__ == '__main__':
print("this file has been executed directly")
else:
print("this file has been imported directly")
As this section doesn't cover all the Standard Library, you are encouraged to take a look at Standard Library Documentation
length of object
# length of string
len("hello")
# length of list
len([1, 2])
# length of tuple
len(tuple([1, 2]))
# length of set
len({1, 2})
join two lists into one
list1 = [1, 2, 3]
list2 = [10, 20, 30]
# similar length
joined_list = list(zip("abc", list1, list2)) # joined_list = [('a', 1, 10), ('b', 2, 20), ('c', 3, 30)]
# vary length
joined_list = list(zip("abcd", list1, list2)) # joined_list = [('a', 1, 10), ('b', 2, 20), ('c', 3, 30)]
range
function generate an iterable object that give a new value in every iteration instead of making a list values
related section: unpack-variables
end = 6
numbers = list(range(end)) # numbers = [0, 1, 2, 3, 4, 5]
start, end, step = 5, 12, 2
numbers = list(range(start, end, step)) # numbers = [5, 7, 9, 11]
related sections: generate random password, string
Warning: The pseudo-random generators of this module should not be used for security purposes.
For security or cryptographic uses, see the secrets module.
import random
import string
random_0_to_1 = random.random()
random_interval_int = random.randint(1, 10) # 10 included
random_odd_numbers = random.randrange(1, 10, 2) # (start, stop, range)
random_choice = random.choice(["python", "c", "c++", "java"])
multiple_random_choices = random.choices(["python", "c", "c++", "java"], k=2)
random_password = "".join(random.choices(string.ascii_letters + string.digits, k=4))
# in-place shuffle
numbers = [1, 2, 3, 4]
random.shuffle(numbers)
reading modes can be found in documentation
# with open
with open("file.text") as file:
# do something
pass
import webbrowser
webbrowser.open("https://www.google.com")
related section: string
# forge message module
from email.mime.multipart import MIMEMultipart
# attach message body modules
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from string import Template
# send message module
import smtplib
# Path module to read image
from pathlib import Path
# forge message header
message = MIMEMultipart()
message["from"] = "name"
message["to"] = "[email protected]"
message["subject"] = "this is a test"
# forge message body
# plain text
message.attach(MIMEText("hello"))
# html page
template = Template(Path("template.html").read_text())
# template file contents
"""<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
hello $name
</body>
</html>"""
body = Template.substitute(name="ahmad")
message.attach(MIMEText(body, "html"))
# image
message.attach(MIMEImage(Path("example.png").read_bytes()))
# send email
# host and port depends on smtp provider
with smtplib.SMTP(host="smtp.gmail.com", port=587) as smtp:
smtp.ehlo()
smtp.starttls()
smtp.login("[email protected]", "password")
smtp.send_message(message)
print("sent..")
related section: run Python files
sys.argv attribute contains
[python file name, ...Command-line Arguments]
import sys
if len(sys.argv) == 1:
print("USAGE: python main.py <password>")
else:
password = sys.argv[1]
print("Password:", password)
import subprocess
# command: ls -l
# print output on cmd
# completed = subprocess.run(["ls", "-l"])
# capture output in stdout attribute
completed = subprocess.run(["ls", "-l"],
capture_output=True,
text=True)
arguments = completed.args
return_code = completed.returncode
std_error = completed.stderr
stdout = completed.stdout
related section: copy file snippet
shutil provides a high-level operations for files.
from pathlib import Path
import shutil
source = Path("text.py")
target = Path("text.txt")
shutil.copy(source, target)
related section: string
- path can be relative or absolute.
- path for windows:
C:\Program Files\Microsoft
- path for mac and linux:
user/local/bin
- path for windows:
from pathlib import Path
# for windows
# back slash is written twice to escape it
Path("C:\\Program Files\\Microsoft")
# by using literal string, you write back slash only once
Path(r"C:\Program Files\Microsoft")
# for mac\linux
Path("user/local/bin")
# current folder
Path()
# combine path objects and strings
Path() / "ecommerce" / Path("../ecommerce")
# home directory
Path().home()
path object has a useful collection of methods.
from pathlib import Path
path = Path("ecommerce")
path.exists()
path.mkdir("test")
path.is_dir()
path.rmdir()
WindowsPath stands for windows based file system
PosixPath stands for mac\linux based file system
from pathlib import Path
path = Path("ecommerce")
paths = [p for p in path.iterdir() if p.is_dir()]
# paths = [WindowsPath('ecommerce/customers'), WindowsPath('ecommerce/shopping'), WindowsPath('ecommerce/__pycache__')]
method | search by pattern | recursive list |
---|---|---|
iterdir() | No* | No |
globe() | Yes | Yes** |
rglobe() | Yes | Yes |
Note 1: You can filter in iterdir function using an if statement in a comprehensive list.
[p for p in path.iterdir() if CONDITION]
Note 2: globe method can be used to list recursively by prefixing the path with (**).
globe("**/*.py")
from pathlib import Path
path = Path("ecommerce")
# list all files and directories in path
paths = [p for p in path.iterdir()]
# filter by file suffix
py_files = [p for p in path.iterdir() if p.suffix == ".py"]
from pathlib import Path
path = Path("ecommerce")
# search by pattern
py_files = [p for p in path.glob("*.py")]
# search by pattern, recursively
py_files = [p for p in path.glob("**/*.py")]
from pathlib import Path
path = Path("ecommerce")
# search by pattern, recursively
py_files = [p for p in path.rglob("*.py")]
from pathlib import Path
path = Path("test.txt")
path.exists()
path.is_file()
path.rename("text.py")
# remove file
path.unlink()
new_path = path.with_suffix(".py") # new_path = text.py
from pathlib import Path
from time import ctime
path = Path("text.txt")
print(path.stat().st_ctime) # 1631987149.6533422 | system dependent
print(ctime(path.stat().st_ctime)) # Sat Sep 18 20:45:49 2021
from pathlib import Path
path = Path("text.py")
# read text
path.read_bytes() # read file as binary
path.read_text() # read file as string
# write text
path.write_text("text")
path.write_bytes(b'10')
related section: copy file snippet
from pathlib import Path
source = Path("text.py")
target = Path("text.txt")
# copy using path object
target.write_text(source.read_text())
related sections: path, open file
from pathlib import Path
from zipfile import ZipFile
# write a zip file from a directory
with ZipFile("files.zip", "w") as zip_file:
for path in Path("directory").rglob("*.*"):
zip_file.write(path)
# read and extract a zip file
with ZipFile("files.zip") as zip_file:
# list files
files_list = zip_file.namelist()
# get file info
info = zip_file.getinfo("file.txt")
file_size = info.file_size
compress_size = info.compress_size
# extract zip file
zip_file.extractall("extract all")
zip_file.extract("file.txt", "extract")
related section: open file
csv stands for Coma Separated Value
Only open file can be used to open csv files.
with csv reader; you can iterate over a csv file only once.
import csv
# write to file
with open("resource.csv", 'w', newline='') as file:
writer = csv.writer(file)
writer.writerow(["transaction_id", "product_id", "price"])
writer.writerow([1000, 1, 5])
writer.writerow([1001, 2, 15])
# read file
with open("resource.csv") as file:
# you can only iterate over csv object once
reader = csv.reader(file)
# list of rows
data = list(reader)
# read row by row
for row in reader:
print(row)
Either open file or working with files can be used to read and write JSON files.
JSON stands for JavaScript Object Notation
loads()
, dumps()
produce and return a string.
While load()
, dump()
produce and return fp object(a .write()-supporting file-like object).
If the json file contains a non-asci characters, you should set ensure_asci
flag to False
.
import json
from pathlib import Path
languages = [
{"id": 1, "name": "Python", "type": "interrupted"},
{"id": 2, "name": "c", "type": "compiled"}
]
# generate json string
data = json.dumps(languages, indent=4)
# write json file
Path("data.json").write_text(data)
# read json file
data = Path("data.json").read_text()
# load json string into python object
language = json.loads(data)
To download SQLite database engine, head into their official website
It's recommended to use an ORM -stands for Object Relational Mapper- to manage a SQLite database, which maps a relational database system to python objects.
SqlAlchemy is one of the best ORMs for python.
import sqlite3
languages = [
{"id": 1, "name": "Python", "type": "interrupted"},
{"id": 2, "name": "c", "type": "compiled"}
]
# insert resource
with sqlite3.connect("db.sqlite3") as connection:
command = "INSERT INTO languages VALUES (?, ?, ?)"
for language in languages:
# execute command
connection.execute(command, tuple(language.values()))
# commit changes
connection.commit()
# retrieve resource
with sqlite3.connect("db.sqlite3") as connection:
command = "SELECT * FROM languages"
# execute command
cursor = connection.execute(command)
# cursor can be iterated over only once
# iterate over cursor
for row in cursor:
print(row)
# fetch all rows
languages = cursor.fetchall()
The epoch is the point where the time starts, and is platform dependent.
import time
timestamp = time.time() # timestamp = 1632115484.41066
# calculate execution time
def send_email():
# sleep for 2 seconds
time.sleep(2)
for i in range(1000):
pass
start = time.time()
send_email()
end = time.time()
duration = end - start
convert timestamp into regular datetime
DateTime object represent a point of time.
from datetime import datetime
from time import time
# datetime from parameters
past = datetime(2020, 1, 1, 11, 59, 59)
# datetime now
present = datetime.now()
# datetime string into object
dt = datetime.strptime("2020/01/01", "%Y/%m/%d")
# timestamp to datetime
dt = datetime.fromtimestamp(time())
print(f"{dt.year}/{dt.month}/{dt.day}")
print(dt.strftime("%Y/%m/%d"))
# comparison
print(past < present)
Time Deltas object represent a duration of time.
from datetime import datetime, timedelta
# increase datetime by 2 days
past = datetime(2020, 1, 1, 11, 59, 59) + timedelta(days=2)
# datetime now
present = datetime.now()
duration = present - past # type(duration) = datetime.timedelta
print(duration) # 626 days, 1:41:55.764459
print(duration.days, "days") # 626 days
print(duration.seconds, "seconds") # 6115 seconds | represent time part of duration
print(duration.total_seconds(), "total seconds") # 54092515.764459 total seconds
walrus operator:=
assigns values to variables as part of a larger expression.
Try to limit use of the walrus operator to clean cases that reduce complexity and improve readability.
a: list = list(range(11))
# regular method
if len(a) > 10:
print(f"List is too long ({len(a)} elements, expected <= 10)")
# the walrus operator
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
from unicodedata import normalize
names = ["Mackintosh", "windows", "linux", "MacDonald's", "NFC"]
allowed_names = ["Mackintosh", "NFC"]
[clean_name.title() for name in names if (clean_name := normalize('NFC', name)) in allowed_names]
del object
pass
statement is used to fill empty blocks.
if True:
pass
related section: coding-conventions
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam) # After local assignment: test spam
do_nonlocal()
print("After nonlocal assignment:", spam) # After nonlocal assignment: nonlocal spam
do_global()
print("After global assignment:", spam) # After global assignment: nonlocal spam
scope_test()
print("In global scope:", spam) # In global scope: global spam
pretty print is an alternative for print which pretty print it's input
print("hello world")
print('*' * 8) # print * 8 times
first = "ali"
last = "ahmad"
# print full name
print(first + " " + last) # concatenation
print(f"{first} {last}") # both 'F' and 'f' are valid
print(first, last)
# expressions
# print(len(first) + " " + 2 + 2) # error | concatenation only accept string
print(f"{len(first)} {2 + 2}")
print(len(first), 2 + 2)
x = input("input: ") # return a string type
print(x)
bin documentation hex documentation
d = 10 # decimal
b = 0b10 # binary
h = 0x12c # hex
print(1) # decimal is the default numbering system
print(bin(1)) # print number as a binary number
print(hex(1)) # print number as a hex number
# a + bi | imaginary number
x = 1 + 2j # x = 1 + 2J
# output: (1+2j)
add = 5 + 2
subtract = 5 - 2
multiply = 5 * 2
divide_with_fraction = 5 / 2 # 2.5
divide_without_fraction = 5 // 2 # 2
reminder = 5 % 2 # 1
power = 5 ** 2 # 25
minus_five = -5
plus_five = +5
x = 1
# apply to all arithmetic operations
x = x + 2
# same as
x += 2
# no increment(x++) or decrement(x--) in python
import math
PI = -3.14
print(round(PI))
print(abs(PI)) # absolute
print(math.floor(PI))
print(math.sqrt(PI))
Note: The Python runtime does not enforce type annotations.
Instead, they can be used by third party tools such as type checkers, IDEs, linters, etc.
# Variable
age: int = 10
age: int
score: float = 4.5
score: float
active: bool = False
active: bool
name: str = "john"
name: str
empty: None = None
x: any
my_list: list[str] = ['a', 'b']
my_tuple: tuple
my_dict: dict
my_set: set
# function
def increment(number: int, by: float, extra: list[str]) -> tuple:
return number, number + by
values: tuple = increment(1, 1.5, ["a", "b"])
related section: function as a variable
int_type = 1
float_type = 1.5
boolean_type = False # True\False
empty = None
# string type
multiple_lines = """first line
second line"""
multiple_lines = '''first line
second line'''
single_line_string = "john"
single_line_character = 'j'
single_line_character = 'john'
# byte type
byte_type = b"10"
Any object can be tested for truth value.
False values are(Anything else is True):
- constants defined to be false:
False
,None
- zero of any numeric type:
0
,0.0
,0j
,Decimal(0)
,Fraction(0, 1)
- empty sequences and collections:
''
,()
,[]
,{}
,set()
,range(0)
# There's no constant variable in python. Therefore, all capital words are used by convenient to inform programmer to not edit value.
PI = 3.14
count = 1 # value only
count: int # type only
count: int = 1 # type and value
# the same can be done with any type of resource
related section: tuple
x = 1
y = 2
# same as
x, y = 1, 2
numbers = (1, 2, 3, 4)
first, second, third, forth = numbers
it's possible to use
match
statement to unpack a variable. go to Patterns with a literal and variable section for more information
related section: tuple
x = 1
y = 2
x, y = y, x # equivalent to: x, y = (y, x)
a = b = 1
a = 1
print(type(a)) # int
a = 3.14
print(type(a)) # float
a = True
print(type(a)) # bool
int(True)
float("1.5")
bool(None)
str(1)
related section: print statement
x_eight_times = "x" * 8
byte_string = b"10"
first = "ali"
last = "ahmad"
# Literal String Interpolation
full = f"{first} {last}"
# raw string
path = r"C:\Program File\Microsoft"
single_line_character = 'j' + 'john' # same as: "j" + "john"
multiple_lines = """first line
second line"""
multiple_lines = '''first line
second line'''
f"{varible=}"
can be used for self-documenting expressions and debugging
course = "python programming"
# change letter case
course.upper()
course.lower()
course.title()
# strip white spaces
course.strip() # from both sides
course.lstrip() # from left side
course.rstrip() # from right side
# find substring
course.find("pro") # case sensitive
# replace text
course.replace("p", "-")
# Split String
course.split(' ')
# join a list of strings separated by ','
','.join(["ab", "cd", "ef"])
# check if sequence of characters exist
print("python" in course) # case sensitive
import string
ascii_letters = string.ascii_letters
digits = string.digits
import string
template = string.Template("hello $name")
message = template.substitute({"name": "ahmad"}) # or
message = template.substitute(name="ahmad")
first = "ali"
last = "ahmad"
# no expressions
full = " ".join([first, last]) # join method
full = first + " " + last # concatenation
# or
full = f"{first} {last}"
# or
full = F"{first} {last}"
# with expressions
full = len(first) + " " + 2 + 2 # error
full = f"{len(first)} {2 + 2}"
course = "python programming"
len(course) # length of string
# access parts of the string
## access individual character | course[index]
first_element = course[0] # 'p'
second_element = course[1] # 'y'
last_element = course[-1] # 'g'
third_element_from_the_end = course[-3] # 'i'
## access range of characters(slice string)
## course[start index:exclusive end index]
course[0:3] # "pyt" | character in index 3 is excluded
course[:3] # "pyt" | start = 0
course[0:] # "python programming" | end = length of string
course[:] # "python programming" | whole string
# the strings below will cause an error
## message = "python "programming"
## message = 'python 'programming'
# in order to deal with that, you can use one of the two methods below:
message = 'python "programming' # 1st method
message = "python \"programming" # 2nd method | escape character
"There are 4 escape Sequences in python: \" \' \n \\"
# sort a list of tuples
items = [("Product1", 10), ("Product2", 9), ("Product3", 12)]
def sort_item(item):
return item[1]
items.sort(key=sort_item) # items = [('Product2', 9), ('Product1', 10), ('Product3', 12)]
# -------------same as(lambda expression)------------
items = [("Product1", 10), ("Product2", 9), ("Product3", 12)]
# items.sort(key=lambda parameter: expression)
items.sort(key=lambda item: item[1]) # items = [('Product2', 9), ('Product1', 10), ('Product3', 12)]
related section: truth values
if conditions:
pass
elif conditions:
pass
else:
pass
if 6 == 5:
pass
elif 6 >= 5:
pass
elif 6 > 5:
pass
elif 6 <= 5:
pass
elif 6 < 5:
pass
elif 6 != 5:
pass
if not (1 == 2) or (1 != 2):
pass
if 2 == 2 and 1 != 2:
pass
# complex expression
x = 5
y = x
if ((x is y) and (x > 4)) or ((x < 4) and (not y == x)):
print("hello")
name = "world"
x = 5
y = x
# in
if x in range(5):
pass
elif x in [1, 2, 4, 6]:
pass
elif name in "hello":
pass
# not
if not name:
print("name is empty")
# not in
elif name not in "hello world":
pass
# is
if x is y:
pass
a = 0
b = 0
# is not
if a is not b:
pass
age = 25
if age >= 18 and age < 65:
print("Eligible")
# same as (chaining comparison operator)
if 18 <= age < 65:
print("Eligible")
age = 25
if age >= 18:
message = "Eligible"
else:
message = "Not eligible"
# same as (Ternary Operator)
message = "Eligible" if age >= 18 else "Not eligible"
print(message)
for character in "python":
print(character)
for character in ['p', 'y', 't', 'h', 'o', 'n']:
print(character)
for number in range(10):
print(number)
else
block will only be executed if the loop is computed successfully without a break
names = ["ahmad", "samir"]
for name in names:
if name.startswith('a'):
print("Found")
break
else:
continue
else:
print("Not found")
for index, value in enumerate(['a', 'b', 'c']):
print(index, value)
generator generates a value in each iteration -unlike lists which stores the values in memory- but has no length because of its nature.
generator has a fixed size regardless of the number of generated values
# declaration
generator = (number * 2 for number in range(5)) # <generator object <genexpr> at 0x00000279738FE900>
for value in generator:
print(value)
# generator size
from sys import getsizeof
generator1 = (number * 2 for number in range(5))
print("a generator of 5 items takes", getsizeof(generator1), "bytes of memory")
# generator of 5 items takes 112 bytes of memory
generator2 = (number * 2 for number in range(100000000))
print("a generator of 100000000 items takes", getsizeof(generator2), "bytes of memory")
# a generator of 100000000 items takes 112 bytes of memory
print("as you can see, generator size is the same no matter how many values can be generated")
print("a list of 100000000 items takes", getsizeof([number * 2 for number in range(100000000)]), "bytes of memory")
# a list of 100000000 items takes 835128600 bytes of memory
comprehensive list is the best practice to map and filter lists
it's recommended to use comprehensive list instead of for loop whenever possible
# for loop
values = []
for number in range(5):
values.append(number * 3)
# comprehensive list
values = [number * 3 for number in range(5)]
items = [("Product1", 10), ("Product2", 9), ("Product3", 12)]
# primitive way
prices = [] # prices = [10, 9, 12]
for item in items:
prices.append(item[1])
# map function
prices = list(map(lambda item: item[1], items)) # prices = [10, 9, 12]
# Comprehensive List
prices = [item[1] for item in items] # prices = [10, 9, 12]
related section: map list
items = [("Product1", 10), ("Product2", 9), ("Product3", 12)]
# primitive way
filtered = [] # filtered = [('Product1', 10), ('Product3', 12)]
for item in items:
if item[1] >= 10:
filtered.append(item)
# filter function
filtered = list(filter(lambda item: item[1] >= 10, items)) # filtered = [('Product1', 10), ('Product3', 12)]
# comprehensive list
filtered = [item for item in items if item[1] >= 10] # filtered = [('Product1', 10), ('Product3', 12)]
related section: filter list
# list
values = [number * 2 for number in range(5)]
# set
values = {number * 2 for number in range(5)}
# dictionary
values = {number: number * 2 for number in range(5)}
# generator
generator = (number * 2 for number in range(5))
head to generator expression section for more info about generator
else
block will only be executed if the loop is computed successfully without a break
related section: for loop
guess = 0
answer = 5
while guess != answer:
guess = input("guess: ")
else:
print("correct guess")
related section: documentation string
def my_function():
pass
# function call
my_function()
There are two type of arguments:
- positional:
function(1)
- keyword:
function(number=5)
# define function
def my_functions(a, b: str, c: str = "default", d="default") -> int:
return 10
# call function
my_functions(1, 'a', c='c')
# my_functions(positional, positional, keyword)
/
indicates that function parameters before it must be specified positionally and cannot be used as keyword arguments.*
indicates that function parameters after it must be specified as keyword arguments and cannot be used positionally.
In the following example :
- parameters a and b are positional-only
- while c or d can be positional or keyword,
- and e or f are required to be keywords:
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
# valid call
f(10, 20, 30, d=40, e=50, f=60)
# invalid call
f(10, b=20, c=30, d=40, e=50, f=60) # b cannot be a keyword argument
f(10, 20, 30, 40, 50, f=60) # e must be a keyword argument
By prefixing a parameter with *
, python will package the passed arguments into a tuple
def multiply(*numbers):
total: int = 1
for number in numbers:
total *= number
return total
multiply(1, 2, 3, 4, 5, 6, 7)
By prefixing a parameter with **
, python will package the passed keyword arguments into a dictionary
def save_user(**user):
print(user) # {'id': 1, 'username': 'admin'}
save_user(id=1, username="admin")
related section: tuple
def increment(a: int, b: int):
return a, a + b # return a tuple
print(increment(5, 2)) # (5, 7)
def addition(x: int):
return x + 3
add = addition
for number in range(6):
print(add(number), end=" ")
# output:
# 3 4 5 6 7 8
name | structure | features |
---|---|---|
list | [1, 'a'] |
read and write list |
tuple | (1, 'a') |
read only list |
set | {1, 2, 3} |
read and write unordered collection of unique values |
dictionary | {"x": 1, "y": 2} |
key-value pairs with no repeated keys |
related sections: named tuple, stack, queue, array
related section: unack list
# declaration
letters = ['a', 'b', 'c', 'd']
matrix = [[1, 2], [3, 4]]
zeros = [0] * 5 # [0, 0, 0, 0, 0]
combined = letters + zeros # concatenation
numbers = list(range(8)) # [1, 2, 3, 4, 5, 6, 7]
characters = list("hello world") # extract characters from a string
# access list items
letters = ['a', 'b', 'c', 'd']
first_item = letters[0]
second_item = letters[1]
last_item = letters[-1]
second_last_item = letters[-2]
# slice list | create a new sliced list
letters = ['a', 'b', 'c', 'd']
print(letters[0:3]) # ['a', 'b', 'c']
print(letters[:3]) # ['a', 'b', 'c']
print(letters[0:]) # ['a', 'b', 'c', 'd']
print(letters[:]) # ['a', 'b', 'c', 'd']
# slice while skipping some items
numbers = list(range(20))
print(numbers[::2]) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
print(numbers[::-2]) # [19, 17, 15, 13, 11, 9, 7, 5, 3, 1]
# modify a list item
letters = ['a', 'b', 'c', 'd']
letters[0] = "A" # ['A', 'b', 'c', 'd']
letters[1:3] = "B", "C" # ['A', 'B', 'C', 'd'] | packing
# list unpacking
numbers = [1, 2, 3, 4]
first = numbers[0]
second = numbers[1]
third = numbers[2]
# ---------same as-------------
first, second, third, forth = numbers # first = 1 | second = 2 | third = 3 | forth = 4
# -----------------------------
first, second, *other = numbers # first = 1 | second = 2 | other = [3, 4]
first, *other, last = numbers # first = 1 | other = [2, 3] | last = 4
# loop over a list
letters = ['a', 'b', 'c']
for letter in letters:
print(letter)
for index, letter in enumerate(letters):
print(index, letter)
# list operations/methods
letters = ['a', 'b', 'd', 'b', 'c', 'e']
# add
letters.append('d') # ['a', 'b', 'd', 'b', 'c', 'e', 'd']
letters.insert(0, '-') # ['-', 'a', 'b', 'd', 'b', 'c', 'e', 'd']
# remove
letters.pop() # ['-', 'a', 'b', 'd', 'b', 'c', 'e']
letters.pop(0) # ['a', 'b', 'd', 'b', 'c', 'e']
letters.remove('b') # ['a', 'd', 'b', 'c', 'e']
del letters[0:3] # ['c', 'e']
letters.clear() # [] | remove all elements of a list
# count occurrence of elements in a list
letters = ['a', 'b', 'b', 'c']
letters.count('b') # 2
# find element index
if 'b' in letters: # index method raise an error if element doesn't exist
letters.index('b')
# sort
# in-place
numbers = [5, 1, 8, 9, 0, 6, 50, 20]
numbers.sort() # numbers = [0, 1, 5, 6, 8, 9, 20, 50]
numbers.sort(reverse=True) # numbers = [50, 20, 9, 8, 6, 5, 1, 0]
# create a new sorted list
numbers = [5, 1, 8, 9, 0, 6, 50, 20]
print(sorted(numbers, reverse=True)) # output: [50, 20, 9, 8, 6, 5, 1, 0] | numbers = [5, 1, 8, 9, 0, 6, 50, 20]
print(sorted(numbers)) # output: [0, 1, 5, 6, 8, 9, 20, 50] | numbers = [5, 1, 8, 9, 0, 6, 50, 20]
related section: comprehensive list
items = [("Product1", 10), ("Product2", 9), ("Product3", 12)]
prices = [item[1] for item in items] # prices = [10, 9, 12]
related section: lambda expression
# sort a list of tuples
items = [("Product1", 10), ("Product2", 9), ("Product3", 12)]
items.sort(key=lambda item: item[1]) # items = [('Product2', 9), ('Product1', 10), ('Product3', 12)]
related section: comprehensive list
items = [("Product1", 10), ("Product2", 9), ("Product3", 12)]
filtered = [item for item in items if item[1] >= 10] # filtered = [('Product1', 10), ('Product3', 12)]
related section: unpack list
numbers = [*range(1, 4)] # [1, 2, 3]
characters = [*"hello"] # ['h', 'e', 'l', 'l', 'o']
mixture = [*numbers, ':', *characters, 2] # [1, 2, 3, ':', 'h', 'e', 'l', 'l', 'o', 2]
read only list
# declaration
my_tuple = (1, 2) # (1, 2)
my_tuple = 1, 2 # (1, 2)
my_tuple = (1,) # (1)
my_tuple = 1, # (1)
empty_tuple = () # ()
# concatenation
my_tuple = (1, 2) + (3, 4) # (1, 2, 3, 4)
# repeat a tuple
my_tuple = (1, 2) * 3 # (1, 2, 1, 2, 1, 2)
characters = tuple("hello") # ('h', 'e', 'l', 'l', 'o')
related section: unpack string
# access tuple items
letters = ('a', 'b', 'c', 'd')
first_item = letters[0]
second_item = letters[1]
last_item = letters[-1]
second_last_item = letters[-2]
# slice tuple | create a new sliced tuple
print(letters[0:3]) # ('a', 'b', 'c')
print(letters[:3]) # ('a', 'b', 'c')
print(letters[0:]) # ('a', 'b', 'c', 'd')
print(letters[:]) # ('a', 'b', 'c', 'd')
# slice and skip some items
numbers = tuple(range(20))
print(numbers[::2]) # (0, 2, 4, 6, 8, 10, 12, 14, 16, 18)
print(numbers[::-2]) # (19, 17, 15, 13, 11, 9, 7, 5, 3, 1)
# check if item exist
numbers = (1, 2, 3, 4)
if 1 in numbers:
print("exist")
# tuples can't be edited
my_tuple = (1, 2)
my_tuple[0] = 2 # error: tuple doesn't support item assignment
# tuple unpacking
numbers = (1, 2, 3, 4)
first = numbers[0]
second = numbers[1]
third = numbers[2]
forth = numbers[3]
# ---------same as-------------
first, second, third, forth = numbers # first = 1 | second = 2 | third = 3 | forth = 4
# swap variables
x = 1
y = 2
x, y = y, x
# pack and unpack
first, second, *other = numbers # first = 1 | second = 2 | other = [3, 4]
first, *other, last = numbers # first = 1 | other = [2, 3] | last = 4
related section: swap variables, unpack variable
unordered unique collection of objects
# declaration
numbers = {1, 2, 2, 3, 4} # numbers = {1, 2, 3, 4}
numbers = set([1, 2, 2, 3, 4]) # numbers = {1, 2, 3, 4}
# add item
numbers.add(1) # numbers = {1, 2, 3, 4}
numbers.add(5) # numbers = {1, 2, 3, 4, 5}
# numbers.remove(10) # error: element doesn't exist
numbers.remove(5) # # numbers = {1, 2, 3, 4}
# as sets are unordered collections, you can't access them by index
numbers[0] # error
# --------operations--------------
first = set([1, 2, 2, 3, 4]) # first = {1, 2, 3, 4}
second = {1, 5}
# union
print("union:", first | second) # {1, 2, 3, 4, 5}
# intersection
print("intersection:", first & second) # {1}
# difference
print("difference:", first - second) # {2, 3, 4}
# semantic difference | in one set or the another, but not both
print("semantic difference:", first ^ second)
# check if exist
if 1 in numbers:
print("exist")
key can be only immutable type
# declaration
point = {'x': 1, 'y': 2}
point = dict(x=1, y=2)
# access dictionary
print(point['x']) # 1
# if key doesn't exist, an error get raised, to deal with that:
# 1. check if key exist
if 'a' in point:
point['a']
# 2. use get method
print(point.get('a')) # if key doesn't exist, 'get' return None
default = 0
print(point.get('a', default)) # if key doesn't exist, 'get' return 0
# assignment
point['z'] = 3 # point = {'x': 1, 'y': 2, 'z': 3}
point['z'] = 2 # point = {'x': 1, 'y': 2, 'z': 2}
# delete item
del point['z']
print(point)
# loop over a dictionary
person = dict(id=1, score=5)
for key in person:
print(key, person[key])
for key, value in person.items():
print(key, value)
it's recommended to use comprehensive list instead of for loop whenever possible
related section: unpack dictionary
There are two ways to merge dictionaries:
**
unpack operatord | other
operator- Create a new dictionary with the merged keys and values of d and other
- which must both be dictionaries
- The values of other take priority when d and other share keys.
d |= other
operatorupdate(other)
function
>>> x = {"key1": "value1 from x", "key2": "value2 from x"}
>>> y = {"key2": "value2 from y", "key3": "value3 from y"}
>>> x | y
{'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}
>>> y | x
{'key2': 'value2 from x', 'key3': 'value3 from y', 'key1': 'value1 from x'}
related section: list
*
is used to unpack a list.
numbers = [*range(1, 4)] # [1, 2, 3]
characters = [*"hello"] # ['h', 'e', 'l', 'l', 'o']
mixture = [*numbers, ':', *characters, 2] # [1, 2, 3, ':', 'h', 'e', 'l', 'l', 'o', 2]
related section: merge dictionaries
**
is used to unpack a dictionary.
first = {'x': 1, 'y': 2}
second = {'x': 10}
combined = {**first, **second, 'z': 3} # {'x': 10, 'y': 2, 'z': 3}
built-in exception documentations
related section: release resource
try:
file = open("app.py")
age = int(input("age: "))
x = 10 / age
except (ValueError, ZeroDivisionError):
print("You didn't enter a valid age.")
else:
print("No exceptions were thrown.")
finally: # this block is used to release resources
file.close()
try:
age = int(input("age: "))
except ValueError as ex:
print("You didn't enter a valid age.")
print(ex) # invalid literal for int() with base 10: 'a'
print(type(ex)) # <class 'ValueError'>
else:
print("No exceptions were thrown.")
try:
age = int(input("age: "))
x = 10 / age
except ValueError:
print("You didn't enter a valid age.")
except ZeroDivisionError:
print("You didn't enter a valid age.")
else:
print("No exceptions were thrown.")
# same as
try:
age = int(input("age: "))
x = 10 / age
except (ValueError, ZeroDivisionError):
print("You didn't enter a valid age.")
else:
print("No exceptions were thrown.")
try:
age = int(input("age: "))
x = 10 / age
except (ValueError, ZeroDivisionError):
print("You didn't enter a valid age.")
except ZeroDivisionError: # this except clause doesn't get executed
print("You didn't enter a valid age.")
else:
print("No exceptions were thrown.")
# output
# age: 0
# You didn't enter a valid age.
only raise an exception if you really have to as they complicate the code and are more costly execution-wise.
You can raise your own exception instead of using the built-in exceptions.
# raise an exception
def xfactor(age):
if age <= 0:
raise ValueError("Age cannot be 0 or less")
return 10 / age
try:
xfactor(0)
except ValueError as error:
print(error)
# return None value
def xfactor(age):
if age <= 0:
return None
return 10 / age
x = xfactor(0)
if x is None:
print("Age cannot be 0 or less")
check calculate runtime for more information about the cost
related section: class
# create a custom exception
class CustomError(Exception):
pass
# raise the exception
raise CustomError
You should release resource after using them.
try:
file = open("app.py")
age = int(input("age: "))
x = 10 / age
except (ValueError, ZeroDivisionError):
print("You didn't enter a valid age.")
else:
print("No exceptions were thrown.")
finally: # this block is used to release resources
file.close()
with
statement can only be used if the object support context management protocol by having the
methods(file.__enter__()
, file.__exit__()
)
try:
with open("app.py") as file: # file get closed after this block get executed
print("file opened.")
age = int(input("age: "))
x = 10 / age
except (ValueError, ZeroDivisionError):
print("You didn't enter a valid age.")
else:
print("No exceptions were thrown.")
with open("app.py") as file, open("readme.md") as target:
print("file opened.")
Change in python 3.10: Enclosing parentheses for continuation across multiple lines.
with (CtxManager() as example):
pass
with (
CtxManager1(),
CtxManager2()
):
pass
with (CtxManager1() as example,
CtxManager2()):
pass
with (CtxManager1(),
CtxManager2() as example):
pass
with (
CtxManager1() as example1,
CtxManager2() as example2
):
pass
with (
CtxManager1() as example1,
CtxManager2() as example2,
CtxManager3() as example3,
):
pass
match
statement is an equivalent to using multiple if conditions to match a single variable against multiple cases
# generic syntax
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case <pattern_4> | <pattern_5> | <pattern_6>:
<action_4>
case _:
<action_wildcard>
Part | Info |
---|---|
case <pattern>: |
the first matching pattern get executed |
case <pattern_4> | <pattern_5> | <pattern_6>: |
if any of the patterns match, the case block get executed |
case _: |
(optional) executed if no case match -similar to default clause- |
patterns don’t match iterators
def http_error(status):
match status:
case 400:
return "Bad request"
case 401 | 403:
return "Not allowed"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
def http_error(status=400):
match status:
case 400 as error_code:
print(f"{error_code}: Bad request")
related section: unpack variable
Patterns may be used to bind variables.
point = (1, 0)
match point:
# unpack and match a tuple
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
print(f"x={x}")
point = {"x": 1, "y": 2}
match point:
case {"x": a, "y": b}:
print(f"X={a}, Y={b}")
point = (1, 0)
match point:
case (x, y):
print(f"X={x}, Y={y}")
# similar to
x, y = point
Patterns may be used to capture class attributes into variables.
class Point:
x: int
y: int
def location(point):
match point:
case Point(x=0, y=0):
print("Origin is the point's location.")
case Point(x=0, y=y):
print(f"Y={y} and the point is on the y-axis.")
case Point(x=x, y=0):
print(f"X={x} and the point is on the x-axis.")
case Point():
print("The point is located somewhere else on the plane.")
case _:
print("Not a point")
class Point2D:
x: int
y: int
class Point3D:
x: int
y: int
z: int
def location(point):
match point:
case Point2D(x=x, y=y):
print(f"2D point: x={x}, y={y}")
case Point3D(x=x, y=y, z=z):
print(f"3D point: x={x}, y={y}, z={z}")
case _:
print("Not a point")
by setting __match_args__
attribute to ("x", "y")
, we can use positional arguments in match
cases. As a result, y
attribute bind to var
variable in all these cases.
class Point:
x: int
y: int
__match_args__ = ("x", "y")
def location(point):
match point:
case Point(1, var):
print(f"var={var}")
case Point(1, y=var):
print(f"var={var}")
case Point(x=1, y=var):
print(f"var={var}")
case Point(y=var, x=1):
print(f"var={var}")
match points:
case []:
print("No points in the list.")
case [Point(0, 0)]:
print("The origin is the only point in the list.")
case [Point(x, y)]:
print(f"A single point {x}, {y} is in the list.")
case [Point(0, y1), Point(0, y2)]:
print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
case _:
print("Something else is found in the list.")
match test_variable:
case ('warning', code, 40):
print("A warning has been received.")
case ('error', code, _):
print(f"An error {code} occurred.")
match [1, 2, 3, 4 , 5]:
# extract first, second, last items
case [x, y, *_, last]:
print(x, y, last) # 1, 2, 5
# extract first, second, rest items
case [x, y, *rest]:
print(rest) # [3, 4, 5]
Value capture happens before the guard is evaluated.
match point:
case Point(x, y) if x == y:
print(f"The point is located on the diagonal Y=X at {x}.")
case Point(x, y):
print(f"Point is not on the diagonal.")
from enum import Enum
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")
The
assert
keyword is used when debugging code
assert
keyword:
- do nothing if condition is true
- raise AssertionError if condition is false
message = "hello"
# if condition return true, no thing happen
assert message == "hello"
# if condition return false, AssertionError is raised
assert message == "good bye" # AssertionError
assert message == "goodbye", "message should be hello" # AssertionError: message should be hello
related section: function
A method is a function that belongs to a class
class Point:
# declare class attribute
number_of_points = 0
def __init__(self, x: str = "variable of a method"):
# access attribute
self.number_of_points += 1
# declare instance attribute
self.x = x
x # parameter
self.x # instance attribute
def instance_method(self):
pass
# make class instance
point = Point()
# access an attribute
point.x
# call instance method
point.instance_method()
# reassign an attribute
point.x = 1
named tuple
-which is an immutable object- can be used for a class of only data.
from collections import namedtuple
Point = namedtuple("Point", ['x', 'y'])
point1 = Point(x=1, y=2)
point2 = Point(x=1, y=2)
print(point1 == point2) # True
# point1.x = 2 # named tuples are immutable
point1 = Point(2, point1.y)
public members are accessible from outside the class
private members are prefixed with
__
which makes them hard to access, but not impossible.
class MyClass:
__private_class_attribute = "private class attribute"
public_class_attribute = "public class attribute"
def __init__(self):
self.__private_instance_attribute = "private instance attribute"
self.public_instance_attribute = "public instance attribute"
# call a private method inside the class
self.__private_method()
# call a public method inside the class
self.public_method()
# access a private attribute inside the class
self.__private_instance_attribute
self.__private_class_attribute
# access a public attribute inside the class
self.public_instance_attribute
self.public_class_attribute
def __private_method(self):
print("private method")
def public_method(self):
print("public method")
my_class = MyClass()
# call/access a public member
# call a public method outside of the class
my_class.public_method()
# access public attributes outside of the class
public_instance_attribute = my_class.public_instance_attribute
public_class_attribute = my_class.public_class_attribute
# assign public attributes outside of the class
my_class.public_class_attribute = "new public class attribute"
my_class.public_instance_attribute = "new public instance attribute"
# call/access a private member!
# call a private method outside of the class
# my_class.__private_method() # raise an error
my_class._MyClass__private_method()
# access a private attributes outside of the class
# my_class.__private_instance_attribute # raise an error
# my_class.__private_class_attribute # raise an error
private_instance_member = my_class._MyClass__private_instance_attribute
private_class_member = my_class._MyClass__private_class_attribute
# assign a private attributes outside of the class
my_class._MyClass__private_instance_attribute = "new private instance attribute"
my_class._MyClass__private_class_attribute = "new private class attribute"
# get list of private and public instance attribute
print(my_class.__dict__)
# {'_MyClass__private_instance_attribute': 'private instance attribute',
# 'public_instance_attribute': 'public instance attribute'}
There are two types of methods:
method type | corresponding parameter | parameter reference to | used to access |
---|---|---|---|
instance method | self |
the instance | instance's methods and attributes |
class method | cls |
the class itself | the class itself |
Note 1: class method should be decorated with
@classmethod
.
Note 2: corresponding parameter -the first parameter in the method- can be named anything, it gets its name by convention.
class MyClass:
# instance method(magic method -> constructor)
def __init__(self, x: int, y: int):
self.x = x
self.y = y
# instance method
def instance_method(self):
pass
# class method
@classmethod
def class_method(cls):
return cls(1, 2)
# create class instance
my_class_obj = MyClass(1, 2) # internally call __intit__ method
my_class_obj = MyClass.class_method() # by calling class_method
# call instance method
my_class_obj.instance_method()
related section: magic method
There are two types of attributes:
attribute type | accessible from | change reflect on |
---|---|---|
class attribute | all instances | all instances |
instance attribute | instance itself | instance itself |
class TagCloud:
# class attribute
tags = []
def __init__(self, hashes):
# instance attribute
self.hashes = hashes
# create instance and assign instance attribute
cloud1 = TagCloud(1)
cloud2 = TagCloud(2)
# assign class attribute
cloud1.tags.append(3)
cloud2.tags.append(4)
# result
print(f"cloud1: tags = {cloud1.tags}, hashes = {cloud1.hashes}") # cloud1: tags = [3, 4], hashes = 1
print(f"cloud2: tags = {cloud2.tags}, hashes = {cloud2.hashes}") # cloud2: tags = [3, 4], hashes = 2
related section: inheritance
class InheritedClass:
pass
class ClassName(InheritedClass):
pass
class_name = ClassName()
isinstance(class_name, ClassName) # True
isinstance(class_name, InheritedClass) # True
issubclass(ClassName, InheritedClass) # True
related section: magic method Propriety is used to set, get, delete an attribute.
It's possible to make a property read only.
# without property
class Product:
def __init__(self, price):
self.set_price(price)
def get_price(self):
return self.__price
def set_price(self, value):
if value < 0:
raise ValueError("Price can't be negative.")
self.__price = value
# set price
product = Product(10)
# get price
product_price = product.get_price()
# reassign price
product.set_price(20)
# with property
class Product:
def __init__(self, price):
self.price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Price can't be negative.")
self.__price = value
# set price
product = Product(10)
# get price
product_price = product.price
# reassign price
product.price = 20
# read-only property
class Product:
def __init__(self, price):
if price < 0:
raise ValueError("Price can't be negative.")
self.__price = price
@property
def price(self):
return self.__price
# set price
product = Product(10)
# get price
product_price = product.price
related section: isinstance and issubclass functions
class Animal:
def __init__(self):
self.age = 1
def eat(self):
pass
# Animal: parent, base
# Mammal: child, sub
class Mammal(Animal):
def walk(self):
pass
class Fish(Animal):
def swim(self):
pass
mammal = Mammal()
mammal.eat()
mammal.walk()
mammal.age
fish = Fish()
fish.swim()
fish.age
from abc import ABC, abstractmethod
class InvalidOperationError(Exception):
pass
class Stream(ABC):
def __init__(self):
self.opened = False
def open(self):
if self.opened:
raise InvalidOperationError
else:
self.opened = True
def close(self):
if not self.opened:
raise InvalidOperationError
else:
self.opened = False
@abstractmethod
def read(self):
pass
class FileStream(Stream):
def read(self):
print("reading resource from a file")
class NetworkStream(Stream):
def read(self):
print("reading resource from a network")
class MemoryStream(Stream):
def read(self):
print("reading resource from a memory")
memory_stream = MemoryStream()
# TypeError: Can't instantiate abstract class MemoryStream with abstract method read
# --------------------------
# class MemoryStream(Stream):
# pass
#
# memory_stream = MemoryStream()
# TypeError: Can't instantiate abstract class Stream with abstract method read
# --------------------------
# stream = Stream()
polymorphism means "many forms"
from abc import ABC, abstractmethod
class UIControl(ABC):
@abstractmethod
def draw(self):
pass
class TextBox(UIControl):
def draw(self):
print("text box")
class DropDownList(UIControl):
def draw(self):
print("drop down list")
def draw(controls):
for control in controls:
control.draw()
text_box = TextBox()
drop_down_list = DropDownList()
draw([text_box, drop_down_list])
# output:
# text box
# drop down list
Duck Typing: the type or the class of an object is less important than the method it defines.
Using Duck Typing, we only check for the presence of a given method or attribute.
The name Duck Typing comes from the phrase:
“If it looks like a duck and quacks like a duck, it’s a duck”
multilevel inheritance can be problematic
class Animal:
def eat(self):
pass
class Bird(Animal):
def fly(self):
pass
class Chicken(Bird):
pass
chicken = Chicken()
chicken.fly() # a chicken can't fly!
multiple inheritance can be problematic
# a bad example
class Employee:
def greet(self):
print("Employee great")
class Person:
def greet(self):
print("Person great")
# python interrupter look for the called method in the class itself.
# If not found, look for it in the next inherited class(Employee in this case)
# If not found, look for it in the next one and so on
class Manager(Employee, Person):
pass
manager = Manager()
manager.greet() # Employee great
# a good example
class Flayer:
def fly(self):
pass
class Swimmer:
def swim(self):
pass
class FlyingFish(Flayer, Swimmer):
pass
# extend str
class Text(str):
def duplicate(self):
return self + self
text = Text("Python")
print(text.duplicate()) # output: PythonPython
# extend list
class TrackableList(list):
def append(self, __object):
print("Append called")
super().append(__object)
trackable_list = TrackableList()
trackable_list.append("a")
# output:
# Append called
super
function is used to access a parent class
class Animal:
def __init__(self):
self.age = 1
class Mammal(Animal):
def __init__(self):
# overridden method won't be executed unless we explicitly call it
super(Animal).__init__()
self.weight = 100
mammal = Mammal()
mammal.age
mammal.weight
A decorator a way to extend the behavior of a method or a function.
# function
@decorator
def function():
pass
# method
class MyClass:
@classmethod
def class_method(cls):
pass
@staticmethod
def get_value(self):
pass
from dataclasses import dataclass
@dataclass
class Point:
x: int
point = Point(1)
related section: inheritance
magic methods get called by python automatically depending on how we use the class. You can change there behavior by ** overriding** them.
magic methods are inherited from a base class for all classes called "object"
class Point:
tags: dict = {"a": 1}
# Constructor
def __init__(self, x, y):
self.x = x
self.y = y
# Defines behavior for the equality operator
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Defines behavior for greater than operator
def __gt__(self, other):
return self.x > other.x and self.y > other.y
def __getitem__(self, item):
return self.tags[item]
def __setitem__(self, key, value):
self.tags[key] = value
# destructor
def __del__(self):
pass
point = Point(10, 20) # __init__
other = Point(1, 2)
print(point == other) # __eq__
print(point > other) # __gt__
print(point < other) # this magic method got figured out by itself
print(point["a"]) # __getitem__
point["b"] = 2 # __setitem__
class Cloud:
def __init__(self):
self.tags: dict = {}
def add(self, key):
self.tags[key.lower()] = self.tags.get(key.lower(), 0) + 1
def __getitem__(self, tag):
return self.tags.get(tag, 0)
# def __setitem__(self, tag, count):
# self.tags[tag.lower()] = count
def __len__(self):
return len(self.tags)
def __iter__(self):
return iter(self.tags)
def __len__(self):
return len(self.tags)
cloud = Cloud() # __init__
cloud.add("Python")
cloud.add("python")
cloud.add("python")
print(cloud["python"]) # __getitem__
# Cloud["b"] = 10 # __setitem__
print(len(cloud)) # __len__
for tag in cloud: # __iter__
print(tag)
# module docstring
"""This module provides a function to convert a PDF to text."""
class Convertor:
# class docstring
"""this class is a simple converter to convert a PDF into a text"""
def convert(self, path: str) -> str:
# function docstring
"""
convert the given PDF into text.
this function takes the given path to read the PDF,
then parse the PDF to extract the text.
:param
path: path to PDF file.
:return:
str: extracted text from the PDF.
"""
return "pdf2text"
open a module documentation in terminal:
pydoc module_name
(windows)pydoc3 module_name
(mac / linux)
export a module documentation into an html file:
pydoc -w module_name
(windows)pydoc3 -w module_name
(mac / linux)
explore documentations in a local server:
pydoc -p port_number
(windows)pydoc3 -p port_number
(mac / linux)
- package: a directory that contains:
- modules: a python file
.py
__init__.py
file, which gets executed once the corresponding module/package get loaded/compiled- sub-package: a package inside a package
- modules
__init__.py
file- and so on...
- modules: a python file
by convention, modules names are:
- lower case
- word separated by
_
modules name shouldn't include space
example: "my_module.py"
import statement look for module name in a list of directories found in
sys.path
one after the other until it finds the module
import sys
print(sys.path)
the codes below are run from an external python file in the same directory as the package
# import multiple objects from a module
from sales import calc_shipping, calc_tax
calc_shipping()
calc_tax()
# sales.py
# │
# ├── calc_shipping function
# └── calc_tax function
# import all objects from a module, bad practice
from sales import *
calc_shipping()
calc_tax()
# sales.py
# │
# ├── calc_shipping function
# └── calc_tax function
# import entire module as an object
import sales
sales.calc_tax()
sales.calc_shipping()
# sales.py
# │
# ├── calc_shipping function
# └── calc_tax function
# "as" expression
import sales as s
s.calc_tax()
s.calc_shipping()
# sales.py
# │
# ├── calc_shipping function
# └── calc_tax function
module in a package
from ecommerce.shopping.sales import calc_shipping, calc_tax
calc_shipping()
calc_tax()
# ecommerce/
# │
# ├── shopping/
# ├──── sales.py
# ├────── calc_shipping function
# └────── calc_tax function
from ecommerce.shopping import sales
sales.calc_shipping()
sales.calc_tax()
# ecommerce/
# │
# ├── shopping/
# ├──── sales.py
# ├────── calc_shipping function
# └────── calc_tax function
module in a sub-package
import ecommerce.shopping.sales as s
s.calc_shipping()
s.calc_tax()
# ecommerce/
# │
# ├── shopping/
# ├──── sales.py
# ├────── calc_shipping function
# └────── calc_tax function
from ecommerce.shopping.sales import calc_tax, calc_shipping
calc_tax()
calc_shipping()
# ecommerce/
# │
# ├── shopping/
# ├──── sales.py
# ├────── calc_shipping function
# └────── calc_tax function
the codes below are run from sales module
# absolute path(best practice)
from ecommerce.customers.contacts import contact
contact()
# ecommerce/
# │
# ├── shopping/
# ├──── sales.py
# ├────── calc_shipping function
# ├────── calc_tax function
# │
# ├── customers/
# ├──── contacts.py
# └────── contact function
# relative path
from ..customers.contacts import contact
contact()
# ecommerce/
# │
# ├── shopping/
# ├──── sales.py
# ├────── calc_shipping function
# ├────── calc_tax function
# │
# ├── customers/
# ├──── contacts.py
# └────── contact function
from ecommerce.shopping import sales
print(dir(sales))
# ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
# '__package__', '__spec__', 'calc_shipping', '']
print(sales.__name__) # ecommerce.shopping.sales
print(sales.__package__) # ecommerce.shopping
print(sales.__file__) # file's path
# ecommerce/
# │
# ├── shopping/
# ├──── sales.py
# ├────── calc_shipping function
# └────── calc_tax function
to make a chunk of module only execute if the module is executed directly(not imported),
you can use __name__ == '__main__'
condition.
if __name__ == '__main__':
print("this file has been executed")
else:
print("this file has been imported")
related section: python implementations
Python generates a compiled version of the imported module with the extension pyc
-in case of cpython implementation- to
speed up the process. And ensure that the compiled version is up-to-date by comparing the dates of making the two files.
Notice: file extension may differ depends on implementation used
The Python Package Index (PyPI) is a repository of software for the Python programming language.
PIP is used to install packages from PyPI using cmd.
Note: mac and linux users write pip3 instead of pip
packages in PyPI use Semantic Versioning
name | command |
---|---|
install latest version of a package | pip install package_name |
install certain version of a package | pip install package_name==2.5.3 |
install latest minor version of a package | pip install package_name==2.5.* , pip install package_name~=2.5.0 |
uninstall a package | unistall package_name |
list installed packages | pip list |
virtual environment is an isolated container for python packages.
There are two types of environments:
type of environment | packages location | get packages location | create operation | activate operation | deactivate operation |
---|---|---|---|---|---|
venv | included in environment | - | py -m venv env_name |
venv/Script/activate.bat (windows), source venv/Script/activate.bat (mac/linux) |
deactivate |
virtualenv | excluded from environment | pipenv -venv |
pipenv install package_name (windows), pipenv3 install package_name (mac/linux) |
pipenv -shell |
exit |
Note 1: virtualenv use "venv" and PIP under the hood.
Note 2: You might need to install virtualenv, by running the commandpip install pipenv
for windows andpip3 install pipenv
for mac / linux.
Note 3: "package_name" in creating pipenv command is optional.
Note 4: To create virtualenv from the dependency in "pipfile.lock" instead of "pipfile" run the commandpipenv install --ignore-pipfile
.
virtualenv make a file called "pipfile", which consists of 3 sections:
- source: the source of the packages
- packages: list of packages used in production
- dev-packages: list of packages used in development only
- requires: specify python version
"pipfile" content:
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
requests = "*"
[dev-packages]
[requires]
python_version = "3.9"
virtualenv dependency operations:
operation | command |
---|---|
list dependencies | pipenv graph |
update outdated packages | pipenv update --outdated |
update a specific outdated package | pipenv update package_name |
pre-requirements:
- Create an account in PyPI.
- install 3 tools globally, by running this command globally
pipenv install setuptools wheel twine
.
Create a package:
directory tree
packege/
│
├── packege/
│ ├── __init__.py
│ ├── module.py
│ └── another_module.py
│
├── tests/
│ ├── test.py
│ └── another_test.py
├── data/
│
├── setup.py
├── LICENSE
└── README.md
"setup.py" content:
import setuptools
from pathlib import Path
setuptools.setup(
name="unique_name",
version="1.2.0",
long_description=Path("README.md").read_text(),
packages=setuptools.find_packages(exclude=["tests", "resource"])
)
"LICENSE" content:
pick a suitable license from choosealicense
create a source distribution and build distribution:
execute the command python setup.py sdist bdist_wheel
for windows or python3 setup.py sdist bdist_wheel
for mac / linux
upload to PyPI by executing the command twine upload dist/*