- Python Essentials 2 - PCAP-30-03 Certification
- Exam Syllabus
- Module 1: Modules, Packages, and PIP
- Module 2: Strings, String Operations and Methods, and Python Exceptions and Errors
- Module 3: Object-Oriented Programming
- Module 4: Miscellaneous
- PCAP-31-03 1.1 – Import and use modules and packages
- import variants: import, from import, import as, import *
- advanced qualifying for nested modules
- the dir() function
- the sys.path variable
- PCAP-31-03 1.2 – Perform evaluations using the math module
- functions: ceil(), floor(), trunc(), factorial(), hypot(), sqrt()
- PCAP-31-03 1.3 – Generate random values using the random module
- functions: random(), seed(), choice(), sample()
- PCAP-31-03 1.4 – Discover host platform properties using the platform module
- functions: platform(), machine(), processor(), system(), version(), python_implementation(), python_version_tuple()
- PCAP-31-03 1.5 – Create and use user-defined modules and packages
- idea and rationale;
- the
__pycache__
directory - the
__name__
variable - public and private variables
- the
__init__.py
file - searching for/through modules/packages
- nested packages vs. directory trees
- PCAP-31-03 2.1 – Handle errors using Python-defined exceptions
- except, except:-except, except:-else:, except (e1, e2)
- the hierarchy of exceptions
raise
,raise Exception(...)
assert
- event classes
except E as e
- the
arg
property
- PCAP-31-02 2.2 – Extend the Python exceptions hierarchy with self-defined exceptions
- self-defined exceptions
- defining and using self-defined exceptions
- PCAP-31-03 3.1 – Understand machine representation of characters
- encoding standards: ASCII, UNICODE, UTF-8, codepoints, escape sequences
- PCAP-31-03 3.2 – Operate on strings
- functions: ord(), chr()
- indexing, slicing, immutability
- iterating through strings, concatenating, multiplying, comparing (against strings and numbers)
- operators: in, not in
- PCAP-31-03 3.3 – Employ built-in string methods
- methods: .isupper(), .islower(), .join(), .split(), .sort(), sorted(), .index(), .find(), .rfind()
- PCAP-31-03 4.1 – Understand the Object-Oriented approach
- ideas and notions:
class
,object
,property
,method
,encapsulation
,inheritance
,superclass
,subclass
,identifying class components
- ideas and notions:
- PCEP-31-03 4.2 – Employ
class
andobject
propertiesinstance
vs.class
variables: declarations and initializations- the
__dict__
property (objects
vs.classes
) - private components (
instances
vs.classes
) - name mangling
- PCAP-31-03 4.3 – Equip a class with methods
- declaring and using methods
- the
self
parameter
- PCAP-31-03 4.4 – Discover the class structure
- introspection and the hasattr() function (objects vs classes)
- properties:
__name__
,__module__
,__bases__
- PCAP-31-03 4.5 – Build a class hierarchy using inheritance
- single and multiple inheritance
- the
isinstance()
function - overriding
- operators:
not is
,is
- polymorphism
- overriding the
__str__()
method - diamonds
- PCAP-31-03 4.6 – Construct and initialize objects
- declaring and invoking constructors
Scope: List Comprehensions, Lambdas, Closures, and I/O Operations
- PCAP-31-03 5.1 – Build complex lists using list comprehension
- list comprehensions: the if operator, nested comprehensions
- PCAP-31-03 5.2 – Embed
lambda
functions into the code- lambdas: defining and using lambdas
- self-defined functions taking lambdas as arguments
- functions:
map()
,filter()
- PCAP-31-03 5.3 – Define and use closures
- closures: meaning and rationale
- defining and using closures
- PCAP-31-03 5.4 – Understand basic Input/Output terminology
- I/O modes
- predefined streams
- handles vs. streams
- text vs. binary modes
- PCAP-31-03 5.5 – Perform Input/Output operations
- the
open()
function - the errno variable and its values
- functions:
.close()
,.read()
,.write()
,.readline()
,.readlines()
- using bytearray as input/output buffer
- the
- importing and using Python modules;
- using some of the most useful Python standard library modules;
- constructing and using Python packages;
- PIP (Python Installation Package) and how to use it to install and uninstall ready-to-use packages from PyPI.
Decomposition is the process of dividing the main parts of your program into smaller more manageable piece.
A Python Module is a file containing python definitions and statements, which can be later imported and used when necessary.
Modules must be Imported using the import
keyword.
# single module
import math
import sys
# multiple modules
import math, sys
A Namespace is a space in which some names exist and the names don't conflict with each other. Inside a certain namespace, each name must remain unique.
If a module of a specified name exists and is accessible, Python imports its contents and the names defined in the module become known, but they don't enter your code's namespace.
Qualified Import
Imports the entire module and makes the modules namespaced entities available via .
dot-notation. No imported entity names conflicts with the identical names existing in your code's namespace.
import math
print(math.sin(math.pi/2))
> 1.0
def sin(x):
return x
def pi():
return 2.0
print(sin(pi/2))
> 1.0
Selective Import
Imported entities are NOT prefixed with .
dot-notation.
from math import sin, pi
print(sin(pi/2))
> 1.0
Import Anti-Pattern
Importing using the *
keyword imports all entities inside a module. AVOID importing all entities to prevent name conflicts.
from math import *
print(sin(pi/2))
Import Aliasing (Named Imports)
An aliase import is a module imported using the as
keyword under an alternate name. After importing a module as an alias, the original module name becomes inaccessible and cannot be used.
import math as m
print(m.sin(m.pi/2))
> 1.0
from math import pi as PI, sin as sine
print(sine(PI/2))
> 1.0
# Ex. 1
import mint
mint.make_money()
# Ex. 2
from mint import make_money
make_money()
# Ex. 3
from mint import make_money as make_more_money
def make_money()
return 100
make_money()
make_more_money()
# Ex. 4
from mint import *
make_money()
The dir() function
shows a list of the entities contained inside an imported module.
import math
print(dir(math))
['cos', 'sin', 'tan', '...']
sin(x) → the sine of x
cos(x) → the cosine of x
tan(x) → the tangent of x
asin(x) → the arcsine of x
acos(x) → the arccosine of x
atan(x) → the arctangent of x
pi → a constant with a value that is an approximation of π
radians(x) → a function that converts x from degrees to radians
degrees(x) → acting in the other direction (from radians to degrees)
sinh(x) → the hyperbolic sine
cosh(x) → the hyperbolic cosine
tanh(x) → the hyperbolic tangent
asinh(x) → the hyperbolic arcsine
acosh(x) → the hyperbolic arccosine
atanh(x) → the hyperbolic arctangent
e → a constant with a value that is an approximation of Euler's number (e)
exp(x) → finding the value of a value e
to the power of x
log(x) → the natural logarithm of x
log(x, b) → the logarithm of x to base b
log10(x) → the decimal logarithm of x (more precise than log(x, 10))
log2(x) → the binary logarithm of x (more precise than log(x, 2))
pow(x, y) → finding the value of xy (mind the domains), this is a unique built in function
ceil(x) → the ceiling of x (the smallest integer greater than or equal to x)
floor(x) → the floor of x (the largest integer less than or equal to x)
trunc(x) → the value of x truncated to an integer (be careful - it's not an equivalent either of ceil or floor)
factorial(x) → returns x! (x has to be an integral and not a negative)
hypot(x, y) → returns the length of the hypotenuse of a right-angle triangle with the leg lengths equal to x and y (the same as sqrt(pow(x, 2) + pow(y, 2)) but more precise)
The random
module allows working with pseudorandom numbers. It can generate numbers that appear random but are created using a deterministic algorithm.
A random number generator uses a seed to calculate a random number and sets a new seed value. Eventually seed values will start repeating, and the generating values will repeat. The seed value determines the order in which the generated values will appear.
Setting the seed with a number taken from the current time will augmented the random factor at each program launch.
random() produces a float number x ranging from (0.0, 1.0) → (0.0 <= x < 1.0)
from random import random
for i in range(5):
print(random())
> 0.9535768927411208
> 0.5312710096244534
> 0.8737691983477731
> 0.5896799172452125
> 0.02116716297022092
seed() directly sets the generator's seed
seed() → sets the seed with the current time
seed(initial) → sets the seed with the integer value initial value
from random import random, seed
seed(0)
for i in range(5):
print(random())
> 0.844421851525
> 0.75795440294
> 0.420571580831
> 0.258916750293
> 0.511274721369
randrange(end) → generates random integer in given range(end)
randrange(beg, end) → generates random integer in given range(beg, end)
randrange(beg, end, step) → generates random integer in given range(beg, end, step)
randint(left, right) → randrange(left, right+1) → generates integer value i
within the range [left, right] (no exclusion on the right side)
print(randint(0, 1))
> 0 or 1
print(randint(0, 2))
> 0, 1, or 2
choice(sequence) → chooses a "random" element from the input sequence and returns it
sample(sequence, elements_to_choose) → builds list (sample) consisting of the # of elements_to_choose element "drawn" from the input sequence
from random import choice, sample
my_list = range(1, 11)
# Choice
print(choice(my_list))
> 4
# Sample
print(sample(my_list, 5))
print(sample(my_list, 10))
> [9, 7, 1, 5, 8]
> [1, 6, 10, 5, 8, 9, 4, 7, 2, 3]
Program Layers:
-
Code is located at the top
-
Python (runtime environment) lies directly below the code
-
OS (operating system) - Python's environment provides some functionalities using the operating system's services
-
Hardware is the bottom-most layer - processor (or processors), network interfaces, human interface devices (mice, keyboards, etc.) and all other machinery needed to make the computer run
The platform
module lets you access the underlying platform's data.
The machine
function returns a generic name of the processor which runs your OS.
The processor()
function returns a string filled with the real processor name.
The system()
function returns the generic OS name as a string.
The version()
function provids the OS version as a string.
from platform import platform, machine, processor, system, version
# platform(aliased = False, terse = False)
print(platform())
print(platform(1))
print(platform(0, 1))
> macOS-12.3.1-arm64-arm-64bit
> macOS-12.3.1-arm64-arm-64bit
> macOS-12.3.1
print(machine())
> arm64
print(processor())
> arm
print(system())
> Darwin
print(version())
> Darwin Kernel Version 21.4.0: Fri Mar 18 00:46:32 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T6000
The python_implementation()
function → returns the current Python implementation
The python_version_tuple()
function → returns a three-element tuple filled with: (major part, minor part, patch level).
from platform import python_implementation, python_version_tuple
print(python_implementation())
> CPython
print(python_version_tuple())
> ('3', '11', '0')
# Ex. 1
import math
print(math.e == math.exp(1))
> True
# Ex. 2
Setting the generator's seed with the same value each time your program is run guarantees that...
...the pseudo-random values emitted from the random module will be exactly the same.
# Ex. 3
The processor() function will determine the name of the CPU running inside your computer.
# Ex. 4
import platform
print(len(platform.python_version_tuple()))
> 3
Packages can contain modules. Packages, like modules, may require initialization.
- While a module is designed to couple together some related entities (functions, variables, constants, etc.), a package is a container which enables the coupling of several related modules under one common name. Such a container can be distributed as-is (as a batch of files deployed in a directory sub-tree) or it can be packed inside a zip file.
- During the very first import of the actual module, Python translates its source code into the semi-compiled format stored inside the pyc files, and deploys these files into the
__pycache__
directory located in the module's home directory. - If you want to instruct your module's user that a particular entity should be treated as private (i.e. not to be explicitly used outside the module) you can mark its name with either the _ or __ prefix. Don't forget that this is only a recommendation, not an order.
- The names shabang, shebang, hasbang, poundbang, and hashpling describe the digraph written as
#!
, used to instruct Unix-like OSs how the Python source file should be launched. This convention has no effect under MS Windows. - If you want convince Python that it should take into account a non-standard package's directory, its name needs to be inserted/appended into/to the import directory list stored in the path variable contained in the sys module.
- A Python file named
__init__.py
is implicitly run when a package containing it is subject to import, and is used to initialize a package and/or its sub-packages (if any). The file may be empty, but must not be absent.
What is an effective way to prevent your module's user from running your code as an ordinary script?
import sys
if __name__ == "__main__":
print "Don't do that!"
sys.exit()
Write code ensuring that all requested modules are imported from the directory D:\Python\Project\Modules
.
import sys
# note the double backslashes!
sys.path.append("D:\\Python\\Project\\Modules")
Assuming the following path and directory structure, import the mymodule
module to be used in a script.
`D:\Python\Project\Modules`
`abc`
|__ `def`
|__ |__ `mymodule.py`
import sys
sys.path.append("D:\\Python\\Project\\Modules")
import abc.def.mymodule
A repository
(or repo for short) designed to collect and share free Python code.
PyPI or Python Package Index, aka. The Cheese Shop, is maintained by the Packaging Working Group
, is part of the Python Software Foundation
, and supports Python developers in efficient code dissemination.
PyPI is a recursive acronym. pip
means “pip
installs packages”, and the pip
inside “pip
installs packages”...
There are two different pip
implementation, one for Python 2 and the other for Python 3.
pip --version
> pip 22.2.2 from /src/python/site-packages/pip
pip help operation
→ shows brief pip's description
pip list
→ shows list of currently installed packages
pip show package_name
→ shows package_name info including package's dependencies
pip search anystring
→ searches through PyPI directories in order to find packages which name contains any string
pip install name
→ installs name system-wide (expect problems when you don't have administrative rights)
pip install --user name
→ install name for you only; no other your platform's user will be able to use it
pip install -U name
→ updates previously installed package
pip uninstall name
→ uninstalls previously installed package
Computers store characters as numbers. There is more than one possible way of encoding characters, but only some of them gained worldwide popularity and are commonly used in IT:
- ASCII (used mainly to encode the Latin alphabet and some of its derivates)
- UNICODE (able to encode virtually all alphabets being used by humans)
It's called I18N
because there is an I at the front of the word, next there are 18 different letters, and an N at the end.
Software I18N is the process of writing a program in a way that enables it to be used all around the world, among different cultures, languages and alphabets.
A Codepoint is a number which makes a character. Codepages makes use of the remaining 128 codepoints to store specific national characters
- The classic form of ASCII code uses
eight bits
for each sign, making available 256 different characters - ASCII occupies 128 out of 256 possible codepoints, you can only make use of the remaining 128
- Codepages helped the computer industry to solve I18N issues for some time, but they would not be a permanent solution.
Unicode
assigns unique (unambiguous) characters (letters, hyphens, ideograms, etc.) to more than a million codepoints.- The first 128 Unicode codepoints are identical to ASCII, and the first 256 Unicode codepoints are identical to the ISO/IEC 8859-1 codepage (a codepage designed for western European languages).
UNICODE
uses different ways of encoding when it comes to storing the characters using files or computer memory: UCS-4 and UTF-8.
A BOM (Byte Order Mark) is a special combination of bits announcing encoding used by a file's content (eg. UCS-4 or UTF-B).
Universal Character Set or USC-4
is a general standard for storing characters in computer memory (encoding).
UCS-4 uses 32 bits (four bytes) to store each character, and refers to the Unicode codepoints' unique number.
Unicode Transformation Format or UTF-8
is the most common standard for encoding characters. UTF-8 uses as many bits for each of the codepoints as it really needs to represent them.
Python3 fully supports Unicode
and UTF-8
and is completely I18Ned
- use Unicode/UTF-8 encoded characters to name variables and other entities
- use them during all input and output
Python strings are immutable sequences.
The backslash character \
is used to escape characters in a string and is not included in a strings length.
empty = ""
print(len(empty))
> 0
i_am = 'I\'m'
print(len(i_am))
> 3
Multiline strings use three single or double quotes surrounding a block of text.
The newline character is whitespace that forces a new line feed. It is invisible or represented as \n
and does count towards the length of string.
multiline = '''Line #1
Line #2'''
print(len(multiline))
> 15
Strings can be concatenated using the addition operator (order matters).
str1 = 'a'
str2 = 'b'
print(str1 + str2)
print(str2 + str1)
> ab
> ba
Strings can be replicated using the multiplication operator (order does not matter).
str1 = 'a'
str2 = 'b'
print(5 * 'a')
print('b' * 4)
> aaaaa
> bbbb
Use the ordinal function ord()
takes a single character as argument and returns the specific character's ASCII/UNICODE code point value. If no character is supplied a TypeError
exception is raised.
char_1 = 'a'
char_2 = ' ' # space
print(ord(char_1))
print(ord(char_2))
> 97
> 32
Use the chr()
function takes a codepoint value and returns its character. Invoking it with an invalid arguement (a negative or invalid code point) causes ValueError
or TypeError
exceptions.
print(chr(97))
print(chr(945))
> a
> α
Since strings are sequences they may be treated like lists in some cases.
Strings can be indexed
by accessing characters in a sequence using list indexing.
the_string = 'silly walks'
for ix in range(len(the_string)):
print(the_string[ix], end=' ')
> s i l l y w a l k s
Strings can also be iterated
through.
the_string = 'silly walks'
for character in the_string:
print(character, end=' ')
> s i l l y w a l k s
Strings can be sliced
also.
alpha = "abdefg"
print(alpha[1:3])
print(alpha[3:])
> bd
> efg
The in
operator checks if its left argument (a string) can be found anywhere within the right argument (another string).
The not
operator negates a boolean value.
alphabet = "abcdefghijklmnopqrstuvwxyz"
print("f" in alphabet)
print("F" in alphabet)
print("1" in alphabet)
> True
> False
> False
Python strings are immutable. This means that once a string is created, it cannot be changed. However, there are many string methods that return a new string instead of modifying the original string.
the_string = 'silly walks'
print(the_string.upper())
print(the_string)
> SILLY WALKS
> silly walks
Strings cannot use the del
instruction to remove characters from a string.
the_string = 'silly walks'
del the_string[0]
> TypeError: 'str' object doesn't support item deletion
Strings cannot use the append()
method to add characters to a string or the insert()
method to insert characters into a string.
the_string.append('!')
> AttributeError: 'str' object has no attribute 'append'
The only thing you can do with del and a string is to remove the string as a whole.
the_string = 'silly walks'
del the_string
print(the_string)
> NameError: name 'the_string' is not defined
The min()
and max()
functions return the smallest and largest characters in a string.
print(min("aAbByYzZ"))
print(max("aAbByYzZ"))
> A
> z
The index()
method returns the index of the first occurrence of a substring within a string. If the substring is not found, a ValueError
exception is raised.
print("aAbByYzZaA".index("b"))
The list()
function returns a list of characters in a string.
print(list("aAbByYzZ"))
> ['a', 'A', 'b', 'B', 'y', 'Y', 'z', 'Z']
The count()
method returns the number of occurrences of a substring in the given string.
print("abcabc".count("b"))
print('abcabc'.count("d"))
> 2
> 0
The capitalize()
method returns a copy of the string with its first character capitalized and the rest lowercased.
print('aBcD'.capitalize())
> ABCD
The center()
method makes a copy of the original string, trying to center it inside a field of a specified width. The original string is returned if width is less than or equal to the length of the original string.
print('[' + 'alpha'.center(10) + ']')
print('[' + 'alpha'.center(11) + ']')
> [ alpha ]
> [ alpha ]
The endswith()
method returns True
if a string ends with the specified suffix. If not, it returns False
.
print("epsilon".endswith("on"))
print("epsilon".endswith("off"))
> True
> False
The startswith()
method returns True
if a string starts with the specified prefix. If not, it returns False
.
print("epsilon".startswith("ep"))
print("epsilon".startswith("epi"))
> True
> False
The find()
method is similar to index()
, but returns -1 if the substring is not found.
print("Eta".find("ta"))
print("Eta".find("mma"))
> 1
> -1
The rfind()
method is similar to find()
, but searches backward in the string instead of forward.
print("tau tau tau".rfind("ta"))
print("tau tau tau".rfind("ta", 9))
print("tau tau tau".rfind("ta", 3, 9))
> 8
> -1
> 4
The replace()
method returns a copy of the string with all occurrences of substring old replaced by new. If the optional argument maxreplace is given, the first maxreplace occurrences are replaced.
print("www.netacad.com".replace("netacad.com", "pythoninstitute.org"))
print("This is it!".replace("is", "are"))
> www.pythoninstitute.org
> Thare are it!
The strip()
method returns a string with leading and trailing whitespace removed.
print("[" + " aleph ".strip() + "]")
> [aleph]
The lstrip()
method returns a string with leading whitespace characters removed from the left side of the string. If you want to remove other characters, you can specify those characters as an argument.
print("[" + " tau ".lstrip() + "]")
print("www.pythoninstitute.org".rstrip("w."))
> [tau ]
> pythoninstitute.org
The rstrip()
method returns a string with trailing whitespace characters removed from the right side of the string. If you want to remove other characters, you can specify those characters as an argument.
print("[" + " upsilon ".rstrip() + "]")
print("www.pythoninstitute.org".rstrip(".org"))
> [ upsilon]
> www.pythoninstitute
The isalnum()
method returns True
if the string consists only of alphanumeric characters (no symbols like !
, @
, and so on). If not, it returns False
.
print("Moooo".isalnum())
print('Mu40'.isalnum())
> True
> True
The isdigit()
method returns True
if the string consists only of digits and is not blank. If not, it returns False
.
print('2018'.isdigit())
print("Year2019".isdigit())
> True
> False
The isalpha()
method returns True
if the string consists only of letters and is not blank. If not, it returns False
.
print("Moooo".isalpha())
print('Mu40'.isalpha())
> True
> False
The islower()
method returns True
if the string is a lowercase string, False
otherwise.
print("MOOO".islower())
print('moooo'.islower())
> False
> True
The isupper()
method returns True
if the string is an uppercase string, False
otherwise.
print("MOOO".isupper())
print('moooo'.isupper())
> False
> True
The isspace()
method returns True
is the string consists only of spaces, tabs, and newlines and is not blank. If not, it returns False
.
print(' \n '.isspace())
print(" ".isspace())
print("moo moo moo".isspace())
> True
> True
> False
The lower()
method returns a string with all uppercase characters converted to lowercase.
print("SiGmA=60".lower())
> sigma=60
The upper()
method returns a string with all lowercase characters converted to uppercase.
print("SiGmA=60".upper())
> SIGMA=60
The swapcase()
method returns a string with uppercase characters converted to lowercase and vice versa.
print("IoTA=130".swapcase())
> iOta=130
The title()
method returns a string with the first character of every word in uppercase and all other characters in lowercase.
print("how now brown cow".title())
> How Now Brown Cow
The join()
method expects one argument as a list and returns a string consisting of the list elements joined by the string.
print(",".join(["omicron", "pi", "rho"]))
print(" ".join(["ABC", "123"]))
> omicron,pi,rho
> ABC 123
The split()
method does the opposite of join()
. It splits the string into a list of elements.
print("phi chi\npsi".split())
print("phi,chi,psi".split(","))
> ['phi', 'chi', 'psi']
> ['phi', 'chi', 'psi']
When comparing, Python compares code point values, character by character.
print("alpha" < "beta")
print("alpha" < "Alpha")
print("alpha" < "alphabeta")
> True
> False
> True
When you compare two strings of different lengths and the shorter one is identical to the longer one's beginning, the longer string is considered greater.
print("alpha" < "alphabeta")
print("alpha" < "alphabeta".upper())
> True
> False
If a string contains digits only, it is still not a number. Consequently, comparing strings against numbers is generally a bad idae.
'10' == 10 > False
'10' != 10 > True
'10' == 1 > False
'10' != 1 > True
'10' > 10 > TypeError exception
Python offers two ways to sort strings. The sorted()
function returns a new sorted list of strings.
words = ["zeta", "Eta", "theta", "Iota"]
sorted_words = sorted(words)
print(sorted_words)
> ['Eta', 'Iota', 'theta', 'zeta']
The sort()
method sorts a list of strings.
words = ["zeta", "Eta", "theta", "Iota"]
words.sort()
print(words)
> ['Eta', 'Iota', 'theta', 'zeta']
The int()
function converts a string to an integer.
print(int("32"))
print(int("Hello"))
> 32
> ValueError exception
The float()
function converts a string to a floating-point number.
print(float("3.14159"))
print(float("45"))
print(float("Hello"))
> 3.14159
> 45.0
> ValueError exception
The str()
function converts a number to a string.
print(str(32))
print(str(3.14159))
> 32
> 3.14159
-
Strings are key tools in modern data processing, as most useful data are actually strings. For example, using a web search engine (which seems quite trivial these days) utilizes extremely complex and complicated string processing, involving unimaginable amounts of data.
-
Comparing strings in a strict way (as Python does) can be very unsatisfactory when it comes to advanced searches (e.g. during extensive database queries). Responding to this demand, a number of fuzzy string comparison algorithms has been created and implemented. These algorithms are able to find strings which aren't equal in the Python sense, but are similar. One such concept is the Hamming distance, which is used to determine the similarity of two strings. Another solution of the same kind, but based on a different assumption, is the Levenshtein distance.
-
Another way of comparing strings is finding their acoustic similarity, which means a process leading to determine if two strings sound similar (like "raise" and "race"). Such a similarity has to be established for every language (or even dialect) separately. An algorithm used to perform such a comparison for the English language is called Soundex and was invented in 1918.
-
Due to limited native float and integer data precision, it's sometimes reasonable to store and process huge numeric values as strings. This is the technique Python uses when you force it to operate on an integer number consisting of a very large number of digits.
Python always raises an exception when it has no idea what to do with your code. Once an exception is raised, Python stops executing the code and returns to the interactive prompt. You can also use the raise
statement to raise an exception.
raise Exception("Something went wrong!")
After an exception is raised, the exception expects to be handled. If it's not handled, the program will crash. You can handle exceptions using the try
/except
statement.
try:
print(1/0)
except ZeroDivisionError:
print("You can't divide by zero!")
- the
except
branches are searched in the same order in which they appear in the code; - you must not use more than one
except
branch with a certain exception name; - the number of different
except
branches is arbitrary - the only condition is that if you use try, you must put at least one except (named or not) after it; - the
except
keyword must not be used without a precedingtry
; - if any of the
except
branches is executed, no other branches will be visited; - if none of the specified
except
branches matches theraised exception
, the exception remains unhandled; - if there is an unnamed
except
branch, it must be the last specified
Python 3 defines 63 built-in exceptions. All python exceptions are derived from the BaseException class. The following diagram shows the exception hierarchy:
If an exception is raised inside a function, it can be handled:
Inside the function:
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print("division by zero!")
else:
print("result is", result)
finally:
print("executing finally clause")
Outside the function:
try:
divide(2, 1)
divide(2, 0)
divide("2", "1")
except TypeError:
print("unsupported operand type(s) for /")
> result is 2.0
> executing finally clause
> division by zero!
> executing finally clause
> unsupported operand type(s) for /
> executing finally clause
The assert
statement is used to check if a condition is true. If the condition is false, an AssertionError
exception is raised.
assert 2 + 2 == 4
assert 1 + 1 == 3
> AssertionError exception
- Some abstract built-in Python exceptions are:
ArithmeticError
BaseException
LookupError
- Some concrete built-in Python exceptions are:
AssertionError
ImportError
IndexError
KeyboardInterrupt
KeyError
MemoryError
OverflowError
In the procedural programming paradigm, the program is a set of procedures (functions) that operate on data (variables). The data is not organized in any particular way, and the functions are not associated with any particular data.
In the object-oriented programming paradigm, the program is a set of objects that interact with one another. Each object is an instance of a particular class, and each class defines a set of attributes that characterize any object of the class. Additionally, each class defines a set of methods that are functions that operate on data that is contained within the class.
A class
is a blueprint for the object. A class
defines the attributes
and methods
that characterize any object that is instantiated from the class.
An object
is an instance of a particular class. An object contains the attributes
and methods
defined by its class. A method
is a function that is defined in a class definition.
Instantiation
is the process of creating an instance of a class. The __init__
method is a special method that is called when an object is instantiated. The __init__
method is used to initialize the attributes of an object.
class Person:
def __init__(self, name):
self.name = name
person = Person("John")
When a class is derived from another class, their relation is named inheritance
Inheritance
is the process by which one class takes on the attributes and methods of another, and expresses an is-a relationship.
Class hierarchy grows from top to bottom, like tree roots, not branches. The topmost class in a hierarchy is called the root class
. The root class is the most general class in the hierarchy. The root class is the superclass of all other classes in the hierarchy.
- (noun) an object has a name that uniquely identifies it
- (adjective) an object has a set of individual properties which make it original and unique
- (verb) an object has a set of abilities to perform specific actions, able to change the object itself, or other objects
Encapsulation
is the process of hiding the implementation details of a class from other objects. Encapsulation is used to hide the data members of a class from other objects.
The stack paradigm follows the rule LILO (Last In, Last Out).
A queue
paradigm follows the rule FIFO (First In, First Out).
class Stack:
def __init__(self):
self.__stack_list = []
def push(self, val):
self.__stack_list.append(val)
def pop(self):
val = self.__stack_list[-1]
del self.__stack_list[-1]
return val
class AddingStack(Stack):
def __init__(self):
Stack.__init__(self)
self.__sum = 0
def get_sum(self):
return self.__sum
def push(self, val):
self.__sum += val
Stack.push(self, val)
def pop(self):
val = Stack.pop(self)
self.__sum -= val
return val
stack_object = AddingStack()
for i in range(5):
stack_object.push(i)
print(stack_object.get_sum())
for i in range(5):
print(stack_object.pop())
Note: objects of the same class do not necessarily have all the same attributes.
class ExampleClass:
def __init__(self, val):
if val % 2 != 0:
self.a = 1
else:
self.b = 1
example_object = ExampleClass(1)
print(example_object.a)
print(example_object.b)
> 1
> AttributeError: 'ExampleClass' object has no attribute 'b'
The hasattr()
function is used to check if an object has an attribute.
class ExampleClass:
def __init__(self, val):
if val % 2 != 0:
self.a = 1
else:
self.b = 1
example_object = ExampleClass(1)
print(hasattr(example_object, 'a'))
print(hasattr(example_object, 'b'))
> True
> False
An instance variable
is a property whose existence depends on the creation of an object
. Every object
can have a different set of instance variables
.
class ExampleClass:
def __init__(self, val):
self.property = val
example_object = ExampleClass(1)
print(example_object.property)
> 1
An instance variable can be private when its name starts with __, but don't forget that such a property is still accessible from outside the class using a mangled name constructed as _ClassName__PrivatePropertyName
class ExampleClass:
def __init__(self, val):
self.__private_property = val
example_object = ExampleClass(1)
print(example_object._ExampleClass__private_property)
> 1
All objects contain a set of built-in properties
properties which are predefined and always available. The dir()
function is used to get a list of all the properties of an object.
class ExampleClass:
def __init__(self, val):
self.property = val
example_object = ExampleClass(1)
print(dir(example_object))
> ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'property']
A class variable
is a property which exists in exactly one copy, and doesn't need any created object to be accessible.
class ExampleClass:
class_property = 1
print(ExampleClass.class_property)
> 1
All a class's class variables are stored inside a dedicated dictionary named __dict__
, which is accessible from the class itself.
class ExampleClass:
class_property = 1
print(ExampleClass.__dict__)
> {'__module__': '__main__', 'class_property': 1, '__dict__': <attribute '__dict__' of 'ExampleClass' objects>, '__weakref__': <attribute '__weakref__' of 'ExampleClass' objects>, '__doc__': None}
A method is a function embedded inside a class. The self
parameter is used to obtain access to the object's instance and class variables.
class ExampleClass:
class_property = 1
def example_method(self):
print(self.class_property)
example_object = ExampleClass()
example_object.example_method()
> 1
The self
parameter is also used to invoke other object/class methods from inside the class.
class ExampleClass:
class_property = 1
def example_method(self):
print(self.class_property)
self.other_method()
def other_method(self):
print("other method")
example_object = ExampleClass()
example_object.example_method()
> 1
> other method
The Constructor:
- is obliged to have the self parameter (it's set automatically, as usual);
- may (but doesn't need to) have more parameters than just self; if this happens, the way in which the class name is used to create the object must reflect the
__init__
definition; - can be used to set up the object, i.e., properly initialize its internal state, create instance variables, instantiate any other objects if their existence is needed, etc.
- cannot return anything, not even None;
- cannot be invoked directly either from the object or from inside the class
The __init__
method is called when an object is created. It is used to initialize the object's properties. If a class has a constructor, it is invoked automatically and implicitly when the object of the class is instantiated.
class ExampleClass:
def __init__(self, val):
self.property = val
example_object = ExampleClass(1)
print(example_object.property)
> 1
The __name__
method is used to get the name of a class.
class ExampleClass:
def __init__(self, val):
self.property = val
example_object = ExampleClass(1)
print(example_object.__class__.__name__)
> ExampleClass
The __module__
method stores the name of the module which contains the definition of the class.
class ExampleClass:
def __init__(self, val):
self.property = val
example_object = ExampleClass(1)
print(example_object.__class__.__module__)
> __main__
The __bases__
method returns a tuple of classes which are direct superclasses for the class.
class SuperOne:
pass
class SuperTwo:
pass
class Sub(SuperOne, SuperTwo):
pass
print(SuperOne.__bases__)
print(SuperTwo.__bases__)
print(Sub.__bases__)
> (<class 'object'>,)
> (<class 'object'>,)
> (<class '__main__.SuperOne'>, <class '__main__.SuperTwo'>)
The type()
function is used to get the type of an object.
class ExampleClass:
def __init__(self, val):
self.property = val
example_object = ExampleClass(1)
print(type(example_object))
> <class '__main__.ExampleClass'>
Introspection
, which is the ability of a program to examine the type or properties of an object at runtime.
Reflection
, which goes a step further, and is the ability of a program to manipulate the values, properties and/or functions of an object at runtime.
class MyClass:
pass
obj = MyClass()
obj.a = 1
obj.b = 2
obj.i = 3
obj.ireal = 3.5
obj.integer = 4
obj.z = 5
def incIntsI(obj):
for name in obj.__dict__.keys():
if name.startswith('i'):
val = getattr(obj, name)
if isinstance(val, int):
setattr(obj, name, val + 1)
print(obj.__dict__)
incIntsI(obj)
print(obj.__dict__)
> {'a': 1, 'b': 2, 'i': 3, 'ireal': 3.5, 'integer': 4, 'z': 5}
> {'a': 1, 'b': 2, 'i': 4, 'ireal': 3.5, 'integer': 5, 'z': 5}
3.5.1.3 OOP Fundamentals: Inheritance
asdf