2 Python Fundamentals Full
2 Python Fundamentals Full
import sys
print(sys.version)
t = True; f = False
print(type(t)) # Print "<class 'bool'>"
print(t and f) # Logical AND; print "False"
print(t or f) # Logical OR; print "True"
print(not t) # Logical NOT; print "False"
print(t != f) # Logical XOR; print "True"
{desmond,kccecia}@ust.hk COMP 2211 (Fall 2022) 9 / 103
Strings
A string is a sequence of characters.
Strings in Python are surrounded by either single quotation marks, or double quotation
marks.
Python has great support for strings.
hello = 'hello' # String literals can use single quotes
world = "world" # or double quotes; it does not matter
print(hello) # Print "hello"
print(len(hello)) # String length; print "5"
hw = hello + ' ' + world # String concatenation
print(hw) # Print "hello world"
hw12 = '%s %s %d' % (hello, world, 12) # sprintf style string formatting
print(hw12) # Print "hello world 12"
print(type(hw12)) # Print "<class 'str'>"
print(hello * 3) # Print "hellohellohello",
# i.e., "hello" is duplicated by 3 times
byte_string = b'COMP2211'
print(type(byte_string)) # Print "<class 'bytes'>"
decode_string = byte_string.decode('utf-8')
print(type(decode_string)) # Print "<class 'str'>"
encode_string = decode_string.encode('utf-8')
print(type(encode_string)) # Print "<class 'bytes'>"
raw_string = r'Hi\nHello'
print(raw_string) # Print "Hi\nHello"
a = 1
b = 2
print(f'a + b = {a+b}') # Print "a + b = 3"
Syntax
<required-datatype>(<expression>)
Parameters:
required-datatype: int, float, bool, str, etc.
expression: The expression to be type-converted
print() function is used to output data to the standard output device (screen).
Syntax
print(*<objects>, <sep>=' ', <end>='\n', <file>=sys.stdout, <flush>=False)
Parameters:
objects: The value(s) to be printed
sep: The separator is used between the values. It defaults into a space character.
end: After all values are printed, end is printed. It defaults into a new line.
file: The object where the values are printed and its default value is sys.stdout (screen).
flush: To ensure that we get output as soon as print() is called, we need to set flush to True.
Output formatting can be done by using str.format() method. {} are used as placeholders,
and we can specify the order in which they are printed by using numbers. Also, we can even
use keyword arguments to format the string.
age = 18
print('I am', age, 'years old') # Print 'I am 18 years old'
print(2, 2, 1, 1) # Print 2 2 1 1
print(2, 2, 1, 1, sep='@') # Print 2@2@1@1
print(2, 2, 1, 1, sep='~', end='*') # Print 2~2~1~1*
print() # Print '\n', i.e., move to the next line
x = 'A+'; y = 'A'
# Print 'Desmond will get A+ and John will get A'
print('Desmond will get {} and John will get {}'.format(x,y))
# Print 'Desmond will get A+ and John will get A'
print('Desmond will get {0} and John will get {1}'.format(x,y))
# Print 'Desmond will get A and John will get A+'
print('Desmond will get {1} and John will get {0}'.format(x,y))
# Print 'Desmond will get A+ and John will get A'
print('Desmond will get {a} and John will get {b}'.format(a = 'A+', b = 'A'))
{desmond,kccecia}@ust.hk COMP 2211 (Fall 2022) 16 / 103
Input Data
We can take the input from the user using input() function.
Syntax
input(<prompt>)
Parameter:
prompt: The string we wish to display on the screen. It is optional.
Note: The entered data is a string.
Indexing/ Changeable/
Container Ordered? Duplicate? Constructor Example
Slicing? Mutable?
List Yes Yes Yes Yes [ ] or list() [5.7, 4, ’yes’, 5.7]
Dictionary No No Yes Yes { } or dict() {’Jun’:75, ’Jul’:89}
Set No No No Yes { } or set() {5.7, 4, ’yes’}
Tuple Yes Yes Yes No ( ) or tuple() (5.7, 4, ’yes’, 5.7)
The list items can be accessed using index operator ([ ] – not to be confused with an
empty list). The expression inside the brackets specifies the index.
The first element has index 0, and the last element has index (# of elements in the list - 1).
Negative index values will locate items from the right instead of from the left.
Syntax
<list-name>.append(<item>)
Parameters:
list-name: The list that we want to append the element to
item: An item (number, string, list, etc.) to be added at the end of the list
The remove() method removes the specified value from the list
Syntax
<list-name>.remove(<element>)
Parameters:
remove the first existence of the element
list-name: The variable name of list that we want to remove an element.
element: The element that we want to remove. If the element does not exist, it throws an exception.
It returns the portion of the list from index “start” to index “end”(exclusive), at a step size
“jump”.
Note: If parameters are omitted, the default value of start, end, and jump are 0, (#elements in the
list), 1, respectively.
# Alternative
# for index in range(3):
# print(animals[index])
# Prints "cat", "dog", "monkey", each on its own line.
i = 0
while i < len(animals):
print(animals[i])
i = i + 1 # Prints "cat", "dog", "monkey", each on its own line.
List comprehension offers a shorter syntax when you want to create a new list based on
the values of an existing list.
Syntax
<new-list-name> = [ <expression> for <element> in <list-name> if <condition> ]
List comprehensions start and end with opening and closing square brackets.
Parameters:
expression: Operation we perform on each value inside the original list
element: A temporary variable we use to store for each item in the original list
list-name: The name of the list that we want to go through
condition: If condition evaluates to True, add the processed element to the new list
new-list-name: New values created which are saved
A dictionary stores (key, value) pairs, similar to a Map in Java or an object in Javascript.
Syntax
<dict-name> = { <key1>:<value1>, <key2>:<value2>, <key3>:<value3>, ... }
Parameters:
dict-name: The name of a dictionary
key1, key2, key3, · · · : The keys
value1, value2, value3, · · · : The values
Note: Dictionary literals are written within curly brackets { }.
Items of a dictionary can be accessed by referring to its key name, inside square brackets.
The get() method returns the value of the item with the specified key.
Syntax
<dict-name>.get(<keyname>, <value>)
Parameters:
dict-name: The name of a dictionary
keyname: The key name of the item you want to return the value from
value: Optional. A value to return if the specified key does not exist. Default value None
The pop() method removes the specified item from the dictionary. The value of the
removed item is the return value of the pop() method.
Syntax
<dict-name>.pop(<keyname>, <default-value>)
Parameters:
dict-name: The name of a dictionary
keyname: The key name of the item you want to remove
default-value: A value to return if the specified key do not exist. If this parameter is not specified,
and the item with the specified key is not found, an error is raised
Syntax
<set-name> = { <value1>, <value2>, <value3>, ...}
Parameters:
set-name: The name of a set
value1, value2, value3, · · · : Set values
Note: Set literals are written within curly brackets { }.
A set is unchangeable (i.e., cannot change the items after the set has been created), and
unindexed. But we can add new items and remove items.
Syntax
<set-name>.add(<element>)
Parameters:
set-name: The name of a set that we want to add an element to
element: The element that we want to add
The remove() method removes the specified element from the set.
Syntax
<set-name>.remove(<element>)
Parameters:
set-name: The name of a set that we want to remove an element from
element: The element that we want to remove
Syntax
<tuple-name> = (<value1>, <value2>, <value3>, ...)
Parameters:
tuple-name: The variable name of a tuple that we want to add element(s) to
value1, value2, value3, · · · : The list of values that we want to add to the tuple
Note: Tuple literals are placed inside parentheses (), separated by commas.
The tuple items can be accessed using index operator ([ ] – not to be confused with an
empty list). The expression inside the brackets specifies the index.
The first element has index 0, and the last element has index (#elements in the tuple - 1).
Negative index values will locate items from the right instead of from the left.
A tuple is in many ways similar to a list; one of the most important differences is that
tuples can be used as keys in dictionaries and as elements of sets, while lists cannot.
Here is a trivial example:
d = {(x, x + 1): x for x in range(10)} # Create a dictionary with tuple keys
t = (5, 6) # Create a tuple
print(type(t)) # Print "<class 'tuple'>"
print(d[t]) # Print "5"
print(d[(1, 2)]) # Print "1"
The zip() method takes iterable or containers and returns a single iterator object, having
mapped values from all the containers.
If the passed iterable or containers have different lengths, the one with the least items
decides the length of the new iterator.
Syntax
zip(<iterator1>, <iterator2>, <iterator3>...)
Parameters:
iterator1, iterator2, iterator3, · · · : Iterator objects that will be joined together
# It prints
# apple is red
# peach is pink
# banana is yellow
# guava is green
# papaya is orange
Normally, when we create a variable inside a function, that variable is local, and can only
be used inside that function.
To create a global variable inside a function, we can use the global keyword.
def assign_word():
global word
word = "cool"
assign_word()
A class is a user-defined data type from which objects are created. It is created by
keyword class.
Class provides a means of bundling data (i.e., instance variables) and functionality (i.e.,
methods) together.
Instance variables:
They are the variables that belong to an object.
They are always public by default and can accessed using the dot (.) operator.
Methods:
They have an extra first parameter, self, in the method definition.
We do not give a value for this parameter when we call the method, Python provides it.
The instance variables and methods are accessed by using the object.
__init__ method is similar to constructors in Java/C++. Constructors are used to
initializing the instance variables of objects.
Syntax Parameters:
class <class-name>: class-name: The name of the
def __init__(self, <arguments0>): class.
self.<instance-variable1> = <value1> arguments0, arguments1,
self.<instance-variable2> = <value2> arguments2, ...: Method
... parameters (i.e., variables)
def <method1-name>(self, <arguments1>): instance-variable1,
<statement1> instance-variable2, ...:
<statement2> Instance variables
... value1, value2, ...: Value
def <method2-name>(self, <arguments2>): assigned to the instance
<statement1> variables
<statement2> statement1, statement2, ...:
... Python statements
Parameters:
object-name: The name of an object
class-name: The name of a class
arguments: The values that we pass to the constructor/method
method-name: The name of the method
instance-variable-name: The name of an instance variable
value: The value assigned to the instance variable
As mentioned, all members in a Python class are public by default, i.e., any member can
be accessed from outside the class. To restrict the access to the members, we can make
them protected/private.
Protected members of a class are accessible from within the class and also available to its
sub-classes (Will not be covered in this course). By convention, Python makes an/a
instance variable/method protected by adding a prefix (single underscore) to it.
Private members of a class are accessible from with the class only. By convention, Python
makes an instance variable/method private by adding a prefix (double underscore) to it.
Note
“By convention” means the responsible programmer would refrain from accessing and modifying
instance variables prefixed with or from outside its class. But if they do so, still works. :(
Modules
A modules in Python is a bunch of related code (functions and
values) saved in a file with the extension .py.
Packages
A package is basically a directory of a collection of modules.
Libaries
A library is a collection of packages.
The import keyword is the most common way to access external modules.
Modules are files (.py) that contain functions and values that you can reference in your
program without having to write them yourself.
This is how you get access to machine learning libraries, like numpy, keras, sklearn,
tensorflow, pandas, pytorch, matpotlib, etc.
import is similar to the #include <something> in C++.
However, you will usually only need to import a few functions from each of those libraries.
In that case, we can use the from keyword to import only the exact functions we are
using in the code.
Syntax
from [module] import [function/value]
For instance, if we only want to import the sqrt function from the math library, we just
need to do:
from math import sqrt # Import only sqrt function from math module
x = 100
# Now, it's imported, we can directly use sqrt to call the math.sqrt()
print(sqrt(x)) # It prints 10
There is also the “from [module] import *” syntax which imports all functions. So
the following code works as well.
from math import * # Import ALL functions from math module
x = 100
print(sqrt(x)) # It print 10
x = 100
# The following will use cmath.sqrt(), which is a different implementation to
# math.sqrt()
print(sqrt(x)) # It print 10
NumPy is a short form for Numerical Python and is the core library for numeric and
scientific computing in Python.
It provides high-performance multidimensional array object, and tools for working with
these arrays.
Expression Description
a[i] Select element at index i, where i is an integer (start counting from 0).
Select the ith element from the end of the list, where n is an integer.
a[-i] The last element in the list is addressed as -1, the second to last
element as -2, and so on.
a[i:j] Select elements with indexing starting at i and ending at j-1.
a[:] or a[0:] Select all elements in the given axis.
a[:i] Select elements starting with index 0 and going up to index i - 1.
Select elements starting with index i and going up to the last element
a[i:]
in the array.
a[i:j:n] Select elements with index i through j (exclusive), with increment n.
a[::-1] Select all the elements, in reverse order.
arr[2] (3,)
arr[2, :] (3,)
arr[2:, :] (1, 3)
# When using integer array indexing, you can reuse the same
# element from the source array:
print(a[[0, 0], [1, 1]]) # Prints "[2 2]"
bool_idx = (a > 2) # Find the elements of a that are bigger than 2; this returns a numpy
# array of Booleans of the same shape as a, where each slot of bool_idx tells
# whether that element of a is > 2.
# We use boolean array indexing to construct a rank 1 array consisting of the elements of a
# corresponding to the True values of bool_idx
print(a[bool_idx]) # Prints "[3 4 5 6]"
Basic mathematical functions operate elementwise on arrays, and are available both as
operator overloads and as functions in the numpy module.
import numpy as np # Elementwise product; both produce the array
# [[ 5.0 12.0]
x = np.array([[1,2],[3,4]], dtype=np.float64) # [21.0 32.0]]
y = np.array([[5,6],[7,8]], dtype=np.float64) print(x * y)
print(np.multiply(x, y))
# Elementwise sum; both produce the array
# [[ 6.0 8.0] # Elementwise division; both produce the array
# [10.0 12.0]] # [[ 0.2 0.33333333]
print(x + y) # [ 0.42857143 0.5 ]]
print(np.add(x, y)) print(x / y)
print(np.divide(x, y))
# Elementwise difference; both produce the array
# [[-4.0 -4.0] # Elementwise square root; produces the array
# [-4.0 -4.0]] # [[ 1. 1.41421356]
print(x - y) # [ 1.73205081 2. ]]
print(np.subtract(x, y)) print(np.sqrt(x))
Matrix multiplication is one of the very important operation for artificial intelligence.
Let’s see how to perform matrix multiplication.
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])
v = np.array([9,10])
w = np.array([11, 12])
# Matrix / vector product; both produce the rank 1 array [29 67]
print(x.dot(v))
print(np.dot(x, v))
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])
v = np.array([9,10])
w = np.array([11, 12])
# Matrix / vector product; both produce the rank 1 array [29 67]
print(np.matmul(x, v))
print(x@v)
x = np.array([[1,2],[3,4]])
x = np.array([[1,2], [3,4]])
print(x) # Print "[[1 2]
# [3 4]]"
print(x.T) # Print "[[1 3]
# [2 4]]"
print(np.transpose(x)) # Print "[[1 3]
# [2 4]]"
print(x.transpose()) # Print "[[1 3]
# [2 4]]"
{desmond,kccecia}@ust.hk COMP 2211 (Fall 2022) 80 / 103
Transpose
The transpose function comes with axes parameter which, according to the values
specified to the axes parameter, permutes the array.
Syntax
np.transpose(<arr>,<axis>)
Parameters:
arr: the array you want to transpose
axis: By default, the value is None. When None
or no value is passed it will reverse the dimensions
of array arr. If specified, it must be the tuple or
list, which contains the permutation of [0,1,..,
N-1] where N is the number of axes of arr.
Note
import numpy as np
v = np.array([1,2,3])
print(v) # Print "[1 2 3]"
print(v.T) # Print "[1 2 3]"
print(np.transpose(v)) # Print "[1 2 3]"
Syntax
numpy.reshape(<arr>, <newshape>)
Parameters:
arr: Array to be reshaped.
newshape: int or tuple of ints
The new shape should be compatible with the original shape. If an integer, then the result
will be a 1D array of that length. One shape dimension can be -1. In this case, the value is
inferred from the length of the array and the remaining dimensions.
Question
How to create the following array in one line of code?
arr = np.array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])
# Answer: arr = np.arange(24).reshape(2,3,4)
{desmond,kccecia}@ust.hk COMP 2211 (Fall 2022) 85 / 103
Broadcasting
Broadcasting is a powerful mechanism that allows numpy to work with arrays of different
shapes when performing arithmetic operations.
Frequently, we have a smaller array and a larger array, and we want to use the smaller
array multiple times to perform some operation on the larger array.
import numpy as np
# We will add the vector v to each row of the matrix x, storing the result in y
x = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
v = np.array([1, 0, 1])
y = np.empty_like(x) # Create an empty matrix with the same shape as x
# Add the vector v to each row of the matrix x with an explicit loop
for i in range(4):
y[i, :] = x[i, :] + v
The above works, however when the matrix x is very large, computing an explicit loop could be slow.
The line y = x + v works even though x has shape (4, 3) and v has shape (3,) due to broadcasting;
this line works as if v actually had shape (4, 3), where each row was a copy of v, and the sum was
performed elementwise.
{desmond,kccecia}@ust.hk COMP 2211 (Fall 2022) 89 / 103
Broadcasting Rules
import numpy as np
A = np.array([1, 2, 3])
Array Shape
print(A.ndim) # Print 1
A ( 3, )
print(A.shape) # Print (3,)
B = np.array([2]) B ( 1, )
print(B.ndim) # Print 1 B has size 1
print(B.shape) # Print (1,)
print(A * B) # Print "[2, 4, 6]"
import numpy as np
A = np.array([1, 2, 3])
Array Shape
print(A.ndim) # Print 1
A ( 1 (Prepended), 3 )
print(A.shape) # Print (3,)
B ( 2, 3 )
B = np.array([[4, 4, 4], [3, 3, 3]])
A has size 1 Same size
print(B.ndim) # Print 2
print(B.shape) # Print (2,3)
print(A*B) # Print [[4 8 12]
# [3 6 9]]
{desmond,kccecia}@ust.hk COMP 2211 (Fall 2022) 92 / 103
Understanding Broadcasting Rules
For each of the following pairs, state whether they are compatible. If they are compatible,
what is the size of the resulting array after performing A*B? How about the following?
1. Pair 1
A: Shape - 5 × 4
B: Shape - 1
2. Pair 2
A: Shape - 5 × 4
B: Shape - 4
1. Yes. Result - 5 × 4
3. Pair 3
A: Shape - 15 × 3 × 5 2. Yes. Result - 5 × 4
B: Shape - 15 × 1 × 5
3. Yes. Result - 15 × 3 × 5
4. Pair 4
A: Shape - 15 × 3 × 5 4. Yes. Result - 15 × 3 × 5
B: Shape - 3 × 5 5. Yes. Result - 15 × 3 × 5
5. Pair 5 6. No.
A: Shape - 15 × 3 × 5
B: Shape - 3 × 1
6. Pair 6
A: Shape - 16 × 6 × 7
B: Shape - 16 × 6
def actualsize(input_object):
memory_size = 0 # memory_size: the actual memory size of "input_object"
# Initialize it to 0
ids = set() # ids: An empty set to store all the ids of objects in "input_object"
objects = [input_object] # objects: A list with "input_object" (traverse from "input_object")
while objects: # While "objects" is non-empty
new = [] # new: An empty list to keep the items linked by "objects"
for obj in objects: # obj: Each object in "objects"
if id(obj) not in ids: # If the id of "obj" is not in "ids"
ids.add(id(obj)) # Add the id of the "obj" to "ids"
memory_size += sys.getsizeof(obj) # Use getsizeof to determine the size of "obj"
# and add it to "memory_size"
new.append(obj) # Add "obj" to "new"
objects = gc.get_referents(*new) # Update "objects" with the list of objects directly
# referred to by *new
return memory_size # Return "memory_size"
try:
ls = ls + 4 # Add 4 to each element of list
except(TypeError):
print("Lists don't support list + int")
# Now on array
try:
arr = arr + 4 # Add 4 to each element of Numpy array
print("Modified Numpy array: ",arr) # Print the Numpy array
except(TypeError):
print("Numpy arrays don't support list + int")
Output:
Lists don't support list + int
Modified Numpy array: [5 6 7]