.. Introduction to Python Lecture 2
Summary Summary: Lists Sets Tuples Variables while loop for loop Functions Names and values Passing parameters to functions
Lists Characteristics of the Python lists ordered collections of arbitrary objects accessible by offset variable-length, heterogenous and arbitrary nestable they represent mutable sequences Examples of lists L = [] #empty list L= [1,2,3,4] #list of numbers L = [1, [2,3], four ] #heterogenous list
Basic list operations List concatenation l1 = [1, 2, 3] l2 = [4, 5, 6] l3 = l1 + l2 print(id(l1)) print(id(l2)) print(id(l3)) print(l3) 4336861000 4337271432 4337271240 [1, 2, 3, 4, 5, 6] List multiplication l1 = ["Python"] l2 = 3 * l1 print(l2) ['Python', 'Python', 'Python'] List membership l = ["Monty", "Python", "and", "the", "Holy", "Grail"] m = "Python" in l print(m) True
Indexing and slicing lists Indexing lists l = ["Monty", "Python", "and", "the", "Holy", "Grail"] print(l[1]) print(l[-1]) Python Grail Slicing lists l = ["Monty", "Python", "and", "the", "Holy", "Grail"] print(l[1:]) ['Python', 'and', 'the', 'Holy', 'Grail'] l = ["Monty", "Python", "and", "the", "Holy", "Grail"] print(l[2:4]) ['and', 'the']
Operations with list elements Extending a list l = ["Monty", "Python"] q = ["and", "the", "Holy", "Grail"] print(l) l.extend(q) ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail'] Reversing a list l = ["Monty", Python, "and", "the", "Holy", "Grail"] l.reverse() print(l) ['Grail', 'Holy', 'the', 'and', 'Python', 'Monty'] Deleting the last element in a list l = ["Monty", Python, "and", "the", "Holy", "Grail"] l.pop() print(l) ['Monty', 'Python', 'and', 'the', 'Holy']
Deleting elements in lists Deleting the i-th element in a list l = ["Monty", Python, "and", "the", "Holy", "Grail"] l.pop(2) print(l) ['Monty', 'Python', 'the', Holy, Grail ] Deleting an element of a list, by value l = ["Monty", Python, "and", "the", "Holy", "Grail"] l.remove( Python') print(l) ['Monty', 'and', 'the', 'Holy', 'Grail'] Deleting an element of a list using del statement l = ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail'] del l[2] print(l) ['Monty', 'Python', 'the', 'Holy', 'Grail']
Matrix representation Matrixes representation (multidimensional lists) 1 2 3 4 5 6 7 8 9 m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print(m[0][2]) print(m[1][1]) 3 5
Changing lists in place Changing by indexing l = ["Monty", "Python"] print(l) l[0] = "Snake" print(l) ['Monty', 'Python'] ['Snake', 'Python'] Changing by slicing l = ["Monty", "Python", "and", "the", "Holy", "Grail"] l[2:4] = ["has", "the"] print(l) ['Monty', 'Python', 'has', 'the', 'Holy', 'Grail'] Slicing is performed in two steps: deletion of range insertion in the place of deletion Be careful when lists are shared references
List appending and sorting Appending a list l = ["Monty", "Python"] l.append(["and", "the", "Holy", "Grail"]) print(l) Sorting a list ['Monty', 'Python', ['and', 'the', 'Holy', 'Grail']] l = ["Monty", "Python", "and", "the", "Holy", "Grail"] l.sort() print(l) ['Grail', 'Holy', 'Monty', 'Python', 'and', 'the'] sort() uses Python standard comparison tests. If other sorting order is needed, a new comparison function should be passed to sort() l = ["Monty", "Python", "and", "the", "Holy", "Grail"] l.sort(key=str.lower) print(l) ['and', 'Grail', 'Holy', 'Monty', 'Python', 'the']
Differences in changing lists Changing a list by appending l = ["Monty"] print(id(l)) l.append(["python"]) print(id(l)) 4540948296 4540948296 Changing a list by concatenation q = ["Monty"] print(id(q)) q = q + ["Python"] print(id(q)) 4541358536 4541359944 Important: when changing a list by appending (and generally speaking, by any method allowing in-place changing) the list remains the same. When changing using concatenation, a new list is built.
Sets Definition: A set is a well defined collection of objects. Sets manipulate unordered collections of unique elements Common use: membership testing removing duplicates from a sequence computing standard math operations on sets such as intersection, union, difference, and symmetric difference. Supported operations: membership testing determining the size of a set iterating through the set Being an unordered collection, set does not support indexing
Operations on sets Creating a set: s = set() print(type(s)) <class 'set'> Adding an element to a set: s = set() s.add("python") print(s) {'Python'} Updating a set: s = set() s.add("python") print(s) new_element = [ Monty ] s.update(new_element) print(s) {'Python'} {'Monty', 'Python'} Update takes an iterable collection as argument, here a list
Updating a set s1 = set(['monty', 'Python']) print(s1) s2 = set(['monty', 'Holy', 'Grail']) print(s2) s1.update(s2) print(s1) {'Monty', 'Python'} {'Monty', 'Grail', 'Holy'} {'Monty', 'Grail', 'Python', 'Holy'} Update a set with another set It works like an union operation. Keep in mind that sets do not support duplicates. One can see that the Monty string occurs only once in the final set
Updating a set (2) Updating a set: s = set() s.add("python") print(s) new_element = Monttty s.update(new_element) print(s) adding an element when the element we update with is not a set, but a string, for example, Python will treat it like a set EQUIVALENT {'Python'} {'y', 'n', 'M', 'Python', 't', 'o'} s = set() s.add("python") print(s) new_element = [ M,'o','n','t', t,'t','y'] s.update(new_element) print(s) One can see that a single t letter was allowed
Other set functons Cardinality of a set (number of elements): s = set([1, 2, 3, 9, 10]) l = len(s) print(l) 5 Checking for membership in a set: s = set([1, 2, 3, 9]) m = 1 in s print(m) True Iterating in a set: s = set([1, 2, 3, 9]) for x in s: print(x) 1 2 3 9
Tuples What is a tuple? A tuple is a sequence of immutable objects. They are somehow similar with lists, but tuples cannot be changed. As notation, they use parentheses () whereas the lists use square brackets []. Creating a tuple t = 1, 2, 3 print(type(t)) print(t) u = (1, 2, 3) print(type(u)) print(u) <class 'tuple'> (1, 2, 3) <class 'tuple'> (1, 2, 3) Tuples are immutable. t = 1, 2, 3 t[0] = 9 print(t) Traceback (most recent call last): File "caller-pkg.py", line 404, in <module> t[0] = 9 TypeError: 'tuple' object does not support item assignment
Tuples Tuples can be nested t = (1, 2, 3, (4, (5, 6, 7), 8), 9) print(t) (1, 2, 3, (4, (5, 6, 7), 8), 9) 1 2 Determine the size of a tuple t = (1, 2, 3, (4, (5, 6, 7), 8), 9) print(t) print(len(t)) (1, 2, 3, (4, (5, 6, 7), 8), 9) 5 3 Concatenation of tuples t = (1, 2, 3, (4, (5, 6, 7), 8), 9) print(t) print(len(t)) (1, 2, 3, (4, (5, 6, 7), 8), 9, 10, 11, 12)
Operations on tuples Determine the number of items in a tuple t = (1, 2, 3, (4, (5, 6, 7), 8), 9) print(t) print(len(t)) (1, 2, 3, (4, (5, 6, 7), 8), 9) 5 Search for an item of a tuple t = (1, 2, 3, (4, (5, 6, 7), 8), 9) i = t.index(3) print(i) 2 But how do we search for a nested item of a tuple? t = (1, 2, 3, (4, (5, 6, 7), 8), 9) i = t[3][1].index(6) print(i) 1 Count the occurrence of an item of a tuple t = (1, 2, 11, 111, 22, 3, 1, 3, 4, 5, 5) c = t.count(1) print(c) 2
print function redirect printing to a file print(value,..., sep=' ', end='\n', file=sys.stdout, flush=false) Method 1 (temporary redirection) print("hello world", file=open("/tmp/log.txt", "a")) Method 2 (globally redirection) import sys tmp = sys.stdout sys.stdout = open( /tmp/log.txt", "a") print("hello world") sys.stdout = tmp # restored print("hello world ) # console again
Variables Naming variables: (underscore or letter) + (any number of letters, digits or underscores) ex: _counter, counter, counter_21_ Variables are case sensitive: _counter is different from _Counter Do not use reserved words as variable names (Python 3 reserved words) False class finally is return None continue for lambda try True def from nonlocal while and del global not with as elif if or yield assert else import pass break except in raise
Variable names conventions Naming conventions: Avoid patterns like name Names beginning with a single underscore _X are not imported by a from module import * Names having two leading and trailing underscores X are system defined, having special meaning to the interpreter Names having only two leading underscores X are mangled to enclosing classes (e.g. a variable called X inside a Spam class will be expanded to _Spam X) Python does not have access modifiers (private, protected, public) By convention, a variable name starting with one underscore _X is considered private in the module scope and starting with two underscores X in the class scope.
Variables Variable creation A variable (and by variable we understand name) is created at the moment in which it is assigned with a value. Future assignments only change the value of an already created name. Variable types A variable does not have ANY information about its type associated with it. The type lives with the OBJECT not with name. Variable use When a variable appears in an expression, it is immediately replaced with the object that is currently refers to. All variables must be explicitly assigned before they can be used. Trying to use an unassigned variable leads to errors.
scope of the variables Functions define a local scope while modules define a global scope module -> file The global scope spans a single file only. There is no notion about global allencompassing file-based scope in Python LEGB scope rule
example of variables scope x = 69 def func(y): z = x + y return z print(func(1)) x has a global scope. It is assigned at the top of the level of the module y and z have local scope. They are both assigned values in the function definition 70 x = 69 def func(y): x = 99 z = x + y return z print(func(1)) print(x) We have two variables named x, one defined at the top level, the other one is defined inside the function. 100 69
global statement Is it possible to change the scope of a variable in Python? x = 99 def func(): global x x = 100 func() print(x) changes the scope of a variable, making it to be module-scope One can notice here that after exiting the body of the function, the value of x remains changed, is not 99 anymore 100 Don t use it unless is absolutely necessary
while loop while loop repeatedly executes a block of statements as long as the test in the top is evaluated as True while <test>: <statements1> else: <statements2> example i = 2 while 0 < i < 3: print(i) i -= 1 else: print("while false condition") 2 1 while false condition
break statement break jumps out of the closest enclosing loop s = 'python' found = False i = 0 while not found: if s[i] == 'y': found = True print( we've found it") break else: i += 1
continue statement continue jumps to the top of the closest enclosing loop s = 'python' found = False i = 0 while not found: if s[i] = 'y': i += 1 continue else: print( we've found it ") found = True
pass statement pass does nothing at all, it is an empty statement placeholder Sometime the syntax requires a statement but there isn t anything useful to say s = 'python' i = 0 while i <len(s): if s[i] == 'h': pass else: print(s[i]) i += 1 p y t o n
for loop (I) for loop is a generic sequence iterator in Python, it steps through the items of any ordered sequence object. for <target> in <object>: <statements> else: <statements> example (iteration over a list) for i in ['python', 'is', 'the', 'best']: print(i) python is the best
for loop (II) example (iteration over a tuple) for i in ('python', 'is', 'the', 'best'): print(i) nested for loops l1 = ["Python", "is", "a", "snake"] l2 = ["Monty", "Python", "and", "the", "Holy", "Grail"] for i in l1: for j in l2: if i == j: print("we have found a duplicate " + j); we have found a duplicate Python
loop coding techniques for loop is usually simpler to code and faster to run than the while loop What if we need to iterate every second item in a list? range() method returns a list of successively higher integers which could be used as indexes in a for loop range(5) -> [0,1,2,3,4] range(2,5) -> [2,3,4] range(0,10,2) -> [0,2,4,6,8] example for i in range(0,10,2): print(i) 0 2 4 6 8
Functions FUNCTIONS Why use functions? Maximising code reuse Minimising redundancy Procedural decomposition Improving code clarity Information hiding
def statements def statements def <name>(arg1, arg2,, argn): <statements> def <name>(arg1, arg2,, argn): <statements> return <value> def is executable code def creates an object and assigns it to a name return sends a result object to the caller arguments are passed by assignment (object reference) (we ll see later how)
Where to define functions? One can declare a function: inside a module inside a class inside another function Functions defined inside a class are called methods
examples of function definition Many ways to define a function in Python module level function level class level def module_func(): print("i am a module level function") module_func() def outer_func(): def inner_func(): print("i am in the inner_func") inner_func() out = outer_func() class Debug(object): def init (self, dbg): self.dbg = dbg def debug(self, message): if self.dbg: print(message) dbg = Debug(True); dbg.debug("hello world")
Functions are objects Functions in Python are objects so they can be manipulated like any other objects. They are called first-class citizens. def func(): """Example of python function""" print("hello from func") print(isinstance(func, object)) print(id(func)) print(func. doc ) print(func. name ) OUTPUT: True 4534929608 Example of python function func
names and values x = 69 x 69 Python assignment statement associates a symbolic name on the left-hand side with a value, on the right-hand side. We say that names refer to values, or a name is a reference to a value x y x = 69 y = x 69 Many names can refer to the same value. Neither x or y is the real name. They refer to the value equally, exactly in the same way. x = 96 x 96 Assigning a new value to x will create another reference. y 69
assignment never copies data Assigning does not copy data. Ever. x y x = 69 y = x 69 We have only one value and two names.
modifying an immutable type What happens if we modify a value? x = 69 y = x x = x + 1 After the first assignment, x is an integer variable. But integers are immutable. So they cannot be modified. This means a new value is created. x y 69 x y 70 69 x = 1 y = x print("x = " + str(x)) print("y = " + str(y)) print("x address : " + hex(id(x))) print("y address : " + hex(id(y))) x = x + 1 print("x = " + str(x)) print("y = " + str(y)) print("x address after assignment : " + hex(id(x))) print("y address after assignment : " + hex(id(y))) x = 1 y = 1 x address : 0x10b9e09e0 y address : 0x10b9e09e0 x = 2 y = 1 x address after assignment : 0x10b9e0a00 y address after assignment : 0x10b9e09e0
assigning for complex python types What happens with the more complex types? loto = [1, 6, 9, 12, 43, 47] loto 1 6 9 12 43 47 prono = loto loto 1 6 9 12 43 47 prono
Mutable types What happens if we modify a mutable type? loto.append(49) loto 1 6 9 12 43 47 49 prono loto = [1, 6, 9, 12, 43, 47] prono = loto print(loto) print(prono) print(hex(id(loto))) print(hex(id(prono))) loto.append(49) print(loto) print(prono) print(hex(id(loto))) print(hex(id(prono))) [1, 6, 9, 12, 43, 47] [1, 6, 9, 12, 43, 47] 0x10cd32f48 0x10cd32f48 [1, 6, 9, 12, 43, 47, 49] [1, 6, 9, 12, 43, 47, 49] 0x10cd32f48 0x10cd32f48
garbage collection Values live until nothing references them. There is a mechanism called garbage collection which takes care of cleaning all the values which are no more referenced. One of the garbage collection components is reference counting. x = 69 y = 96 x = y x 69 y 96 x 69 y 96
passing parameters to functions When passed to a function, x and y arguments are assigned to a value as like any other name to value assignment def addition(x, y): return (x + y) print(addition(33, 66)) The names x and y are local to the addition function, so when the function ends, the names go away.
passing arguments to a function What if the parameter passed to a function has its value referenced by another name? def change_list(l, val): l.append(val) my_list = [2, 4, 6] change_list(my_list, 8) print(my_list)
def change_list(l, val): l = l + [val] my_list = [2, 4, 6] change_list(my_list, 8) print(my_list) passing arguments to a function
Default parameters for functions Python has the ability to handle default parameters for functions. For the languages that do not support default parameters, the trick is to create multiple functions with multiple parameters and call them appropriately. Example 1 def log(message=""): if message: print(message) log() log("hello World") Hello World Notice that for the call with empty params, nothing was displayed
Default parameters for functions Example 2 def bad_default(val, l=[]): l.append(val) return l print(bad_default(3)) print(bad_default(4)) Expected [3] [4] Real [3] [3, 4] Why? A new list is created ONCE when the function is defined, then the same list is used in each call. Python s default arguments are evaluated only ONCE, when the function is defined and not each time the function is called. So, if a mutable default argument is used and change it, it will be used for all the future calls of that function.
Default parameters for functions Solution def good_default(val, l=none): if l is None: l = [] l.append(val) return l print(good_default(3)) print(good_default(4)) Expected [3] [4] Real [3] [4] Instead of using a mutable argument for the placeholder, use the special type None. This will signal Python that no argument was provided and create a new object every time the function is called.
Conclusions Conclusions Lists are a very important type in Python Variables have a name convention and they belong to different scopes Looping is assured by while and for statements Functions are building blocks of a Python program Names and values are strongly tided in Python Passing arguments to functions is made by-assignment Python supports default parameters for functions