Python
CSE 216 : Programming Abstractions
Computer Science Stony Brook University
Dr. Ritwik Banerjee
A Brief Background
• Development in the late 1980s headed by Guido Van Rossum.
• Python 2.0 was released in 2000.
• Python 3.0 was released in 2008.
• Unlike Java, Python 3.x is not backward compatible.
• That is, if you write a program in a version 2.x., it
may not work on modern Python interpreters.
• The official Python wiki says “Python 2.x is legacy, Python 3.x is the present and future of the language”. So, we will use Python 3.x in this course.
• For additional help
• The standard Python documentation
(https://docs.python.org/3/) contains a tutorial.
• The beginner’s guide to Python (https://wiki.python.org/moin/BeginnersGuide)
Coding Environment
• •
Many computers come pre-installed with Python. If not, you can download and install it from https://www.python.org/downloads/.
The Python interpreter can be launched straight-away from the terminal. This is an interactive shell, and perhaps the best way to get started!
© 2019 Ritwik Banerjee
2
For complex projects, you should use a IDE
• PyCharm
• PyDev + Eclipse.
The Python installation also usually comes with a GUI environment called IDLE, which contains a Python shell.
© 2019 Ritwik Banerjee 3
Coding Environment
Running a simple program
# Welcome.py
# Display the one true message
# In case you haven’t realized,
# the hash marks a comment line.
print(“Hello World!”)
# No semi-colon needed at the end of a statement
© 2019 Ritwik Banerjee 4
Syntax: blocks and indents
• Instead of brackets, Python uses indented block structures.
• So, instead of
if (x) {
if (y) {
} a_subroutine();
another_subroutine();
}
• Python looks like:
if xi:f ya:_subroutine()
another_subroutine()
• The convention is to use 4 spaces (not tabs).
• This is not a formal requirement, but using anything other than 4 spaces will make your code incompatible with most other python codes.
Objects
• Python is an object-oriented language.
• Everything – including numbers – is an object.
• Every object has an id() and a type()
• The id remains unchanged from the creation until the destruction of the binding between the name and the object.
• In the C implementation, the id is address of the object in memory.
© 2019 Ritwik Banerjee
6
Built-in Types
Boolean Type
Numeric types
Sequence Types
Mappings
Classes
Instances
Exceptions
© 2019 Ritwik Banerjee 7
Boolean Type
• Anobjectisconsideredtruebydefault,unlessitsclass defines either a __bool__() method that
returns False or a __len__() method that returns zero.
• Thecommonbuilt-inobjectsthatareequivalenttofalseare
a) constants defined to be false: None and False.
b) zero of any numeric type
c) empty sequences and collections
• Thebooleanoperationsareand,or,not.
• Withand,thesecondargumentonlygetsevaluatedif
the first argument is true.
• Withor,thesecondargumentonlygetsevaluatedif the first argument is false.
• InPython,Booleansareasubtypeofintegers. • 0isfalse,everythingelseistrue.
Numeric Types
• Python has three numeric types (not considering Boolean):
1. Integers
2. Floating-point numbers
3. Complex Numbers
• Aninthasunlimitedprecision,whichiswhythereisno separate data type called ‘long’ in Python.
• AfloatareimplementedasdoubleinC.
• A complex is a complex number (e.g., 5 + 3j), implemented as two floating-point numbers denoting the real and imaginary .parts respectively
Type Conversion
Python performs the following type conversions for numbers:
• int(4.2) → 4
• float(4) → 4.0
round(4.51) 5
•→ • round(4.5) → 4 • int(4.63) → 4
The following functions use the math module:
Built-in functions for numeric types
• max(2,3,4)
• min(2,3,4)
• round(4.51)
• abs(-3.2)
• pow(3,2)
• pow(3.1,2.2)
#4
#2
# 5
# 3.2
# 9 (same as 3**2)
# 12.050240825798763
Sequence Types
Lists
Tuples
Ranges
Two additional sequence types: String, and Binary data
© 2019 Ritwik Banerjee 12
Common Sequence Operations
Source: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
© 2019 Ritwik Banerjee 13
Lists
• A list is a mutable sequence.
• It can be heterogeneous, but usually, programmers use
homogeneous lists.
• List construction:
1. a_list = [] # create an empty list
2. a_list = list() # same as line 1.
3. a_list = [2,3,4] # create a list with these three integers
4. a_list = [‘red’, ‘green’, ‘blue’]
# create a list with these three strings
5. a_list = list(‘red’)
# create the list [‘r’, ‘e’, ‘d’]; this is
# possible because a string is a sequence of # characters
List methods
We are using the python syntax for method signature below, which is:
•append(x: object): None •insert(index: int, x: object): None •remove(x: object): None
•index(x: object) int
•count(x: object): int
•sort(): None
•reverse(): None
•extend(l: list): None
© 2019 Ritwik Banerjee 15
Built-in Functions with Lists
Operators on Lists
List Comparisons
© 2019 Ritwik Banerjee 18
Tuples
• A tuple is an immutable sequence, typically used to store collections of heterogeneous data.
• It is the record type in Python. • Constructing a tuple:
>>> a = (11, 22, 33)
>>> b = (‘a’, ‘b’, ‘c’, ) # note the last comma
>>> c = 12, 23 # the comma defines a
tuple, the brackets are optional
>>> type(c)
>>> c
(12, 23)
Heterogeneous Tuples
• For records, it usually makes more sense to access the data using names instead of indices.
• Wecanusecollections.namedtuple().Thisisnota built-in type.
>>> from collections import namedtuple
>>> Phone namedtuple(‘Phone’, [‘owner’, ‘number’])
>>> p = Phone(‘John’, ‘1231231231’)
>>> p
Phone(owner=’John’, number=’1231231231′) >>> p[0]
‘John’
>>> p.owner
‘John’
Heterogeneous Tuples
• For records, it usually makes more sense to access the data using names instead of indices.
• Wecanusecollections.namedtuple().Thisisnota built-in type.
>>> q = Phone(‘Jack’, 1231231231)
>>> q.number
1231231231
>>> owner, number = q
>>> owner
‘Jack’
>>> number
1231231231
>>> tuple(“apple”)
(‘a’, ‘p’, ‘p’, ‘l’, ‘e’)
Ranges
• The range data type represented immutable sequences of numbers.
• There are two ways of constructing a range:
>>> range(10) # ‘start’ has a default value of 0
range(0, 10)
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(0, 10, 3)) # ‘step’ has a default value of 1.
[0, 3, 6, 9]
>>> list(range(0, 10, -2))
[]
>>> list(range(0, -10, -2)) # count down with negative step
[0, -2, -4, -6, -8]
Ranges
• The range data type represented immutable sequences of numbers.
• What happens when you print a range?
>>> print(range(10)
range(0, 10)
>>> print(list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
• In many ways the object returned by range() behaves like a list, but it isn’t.
• It is an object that returns the successive items of the desired sequence when you iterate over it, but the sequence is not pre-constructed.
Strings
• AstringinPythonisanimmutablesequenceofcharacters.
• Tobemoreprecise,animmutablesequenceofUnicode
code points.
• Internally,itisimplementedthesamewayasinC,i.e.,a
character array terminated by the NULL character \0. s1 = ‘a string can be defined in single
quotes’
s2 = “or double quotes”
s3 = “””for multiline string,
three double quotes are
required”””
s4 = ”’three single quotes
work as well”’
• InthePythonshell,youcantypehelp(str)toseethe documentation.
Strings
Constructing strings
>>> s1 = str()
>>> s2 = str(“this is a string”)
>>> s3 = “this is a string”
>>> s1
”
>>> s2
‘this is a string’
>>> s3
‘this is a string’
Strings
Sequence methods on strings
s = “this is a string”
>>> len(s)
16
>>> max(s)
‘t’
>>> min(s)
”
>>> s + s
‘this is a stringthis is a string’
>>> 2*s
‘this is a stringthis is a string’
Strings
Sequence methods on strings
s = “this is a string”
>>> s[2:6] # slicing gives a substring ‘is i’
>>> s[:3] # from index 0 to 3 (excluding) ‘thi’
>>> s[3:] # from 3 (including) to the end ‘s is a string’
>>> s[-1]
‘g’
>>> ‘b’ in s # same as Java’s contains() True
String
Methods
• str.capitalize()
• str.lower()
• str.endswith(suffix)
• str.find(substring)
• Thefind()methodshouldbeusedonlyifyouneed to know the position of substring. Otherwise, in is sufficient.
• A (mostly) complete list of methods to do string processing is available here: https://docs.python.org/3/library/text.html#textservices
• A set is an unordered collection of distinct hashable elements. There are two built-in set implementations: set and frozenset.
Sets
• Afrozensetisimmutable,andwecan’tadd()or remove() elements.
>>> s = {“apple”, “banana”, “mango”} >>> print(type(s))
Dictionaries
• The mapping data type is called a dictionary. It stores key-value pairs of arbitrary hashable elements.
>>> a = dict(one=1, two=2, three=3)
>>> b = {‘one’: 1, ‘two’: 2, ‘three’: 3}
>>> c = dict(zip([‘one’, ‘two’, ‘three’], [1, 2, 3]))
>>> d = dict([(‘two’, 2), (‘one’, 1),
(‘three’, 3)])
>>> e = dict({‘three’: 3, ‘one’: 1, ‘two’: 2})
>>> print(a == b == c == d == e) True
Dictionary operations
a = dict(one=1, two=2, three=3)
print(a.keys())
print(list(a.keys()))
print(list(a.values()))
a[‘four’] = 4 # set key to value
a[‘four’]
a[‘five’]
# return the value with this key
# KeyError if key is not in dictionary
© 2019 Ritwik Banerjee
31
Control Flow
from math import pi
if radius >= 0:
area = radius * radius * pi print(“Radius: ” + str(radius) +
else: “; Area: ” + str(area)) print(“Invalid in put”)
Control Flow
if score >= 90.0: grade = ‘A’
elsei:f score >= 80.0: grade = ‘B’
elsei:f score >= 70.0: grade = ‘C’
elsei:f score >= 60.0: grade = ‘D’
elseg:rade = ‘F’
if score >= 90.0: grade = ‘A’
elif score >= 80.0: grade = ‘B’ elif score >= 70.0: grade = ‘C’ elif score >= 60.0: grade = ‘D’
elseg:rade = ‘F’
© 2019 Ritwik Banerjee
33
Control Flow
words = [‘cat’, ‘window’, ‘defenestrate’] for w in words:
print(w, len(w)) for i in range(10):
print(i**2)
a = [‘Mary’, ‘had’, ‘a’, ‘little’, ‘lamb’] for i in range(len(a)):
print(i, a[i])
Control Flow
for n in range(2, 10): for x in range(2, n):
if n % x == 0:
print(n, ‘equals’, x, ‘*’, n//x) break
else:print(n, ‘is a prime number’)
© 2019 Ritwik Banerjee 35
Control Flow
for num in range(2, 10):
if num % 2 == 0:
print(“Found an even number”, num)
continue
print(“Found a number”, num)
© 2019 Ritwik Banerjee 36
Control Flow
• There is a special statement in Python called pass, which does nothing.
• It can be used when a statement is required syntactically but the program requires no action. Example:
while True: pass
Functions
• The keyword def introduces a function definition.
• Function name
• List of formal parameters
• The first statement can (optionally) be a string literal.
• Python’s way of providing the documentation string (docstring).
def fibseries(n):
“Print a Fibonacci series up to n.”
a, b = 0, 1
while a < n:
print(a, end = ' ')
a, b = b, a + b
print()
© 2019 Ritwik Banerjee 38
Multiple assignments
• The variables a and b simultaneously get the values 0 and 1.
• In the last line of the loop, we use multiple assignments again.
The expressions on the right-hand side are evaluated before any assignment takes place.
The right-hand side expressions are evaluated left-to- right.
• Recall that the comma is implicitly a tuple-construction: b, a + b is the tuple (b, a + b)
def fibseries(n):
"Print a Fibonacci series up to n." a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
print()
Return types
def fibseries(n):
"Print a Fibonacci series up to n." a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b print()
What does the function
fibseries return?
• Evenfunctionswithout
a return statement do return a value: None.
• ThisisthenullvalueinPython, much like void in Java. It is a reserved keyword.
• Asanexample,hereisa function that returns the integer type.
© 2019 Ritwik Banerjee
40
Optional and mandatory parameters
Specify a default value for one or more arguments:
def confirmation(query, max_attempts=3,
reminder='Invalid input.'):
while True:
response = input(query)
if response in ('y', 'Y', 'yes', 'Yes'):
return True
if response in ('n', 'N', 'no', 'No'):
return False
max_attempts = max_attempts – 1 if max_attempts < 0:
raise ValueError('Too many attempts.') print(reminder)
Optional and mandatory parameters
Try to write a similar piece of code to have the behavior as shown here.
© 2019 Ritwik Banerjee 42
• Default values are evaluated at the point of a function definition.
• The default value is evaluated only once.
• This is very important for mutable objects like lists, dictionaries, etc.
How are default values evaluated?
© 2019 Ritwik Banerjee 43
To avoid such sharing of the default value between subsequent function calls, rewrite the function like this.
How are default values evaluated?
© 2019 Ritwik Banerjee 44
Keyword and positional arguments
An argument is a value passed to a function when calling the function. Python provides two kinds of arguments:
• A keyword argument (kwarg) is preceded by an identifier in a function call.
• E.g., construct a complex number by calling complex(real=3, imag=2).
• A positional argument is an argument that is not a keyword argument. These can appear at the beginning of an argument list.
• E.g., construct a complex number by calling complex(3, 2).
© 2019 Ritwik Banerjee 45
Keyword arguments
This function can be called in any of the following ways:
1) confirmation(query='Are you sure?') 2) confirmation(query='Are you sure?',
max_attempts=10)
3) confirmation(query='Are you sure?', max_attempts=10,
reminder='Nope. Try again!') 4) confirmation(query='Are you sure?',
reminder='Nope. Try again!')
def confirmation(query, max_attempts=3,
reminder='Please try again with a valid input.'):
© 2019 Ritwik Banerjee 46
Keyword arguments
This function can be called in any of the following ways:
1) confirmation(query='Are you sure?') 2) confirmation(query='Are you sure?',
max_attempts=10)
3) confirmation(query='Are you sure?', max_attempts=10,
reminder='Nope. Try again!') 4) confirmation(query='Are you sure?',
reminder='Nope. Try again!') But the following are invalid:
1) confirmation() # missing required arg
2) confirmation(query='Are you sure?', 2)
# non-keyword arg after keyword arg
© 2019 Ritwik Banerjee 47
Keyword arguments & positional arguments
Keyword arguments must follow positional arguments.
All the keyword arguments passed must match one of the arguments accepted by the function, and their order is not important.
A required argument may also be provided with a keyword.
No argument may receive a value more than once.
© 2019 Ritwik Banerjee 48
Arbitrary number of arguments
def write_multiple_items(file, separator, *args): file.write(separator.join(args))
def concat(*args, sep=' '): return sep.join(args)
• Afunctioncanbecalledwithanarbitrarynumberofarguments.
• Theseargumentsarewrappedupinatuple.
• Normalargumentsmayoccurbeforethevariablenumberof arguments.
• Therestareeffectivelyscoopedupandpassedtothefunction. • Functionsthattakeavariablenumberofargumentsarecalled
variadic functions.
• Anyformalparameterafterthe*argsmustbeakeyword
argument.
© 2019 Ritwik Banerjee 49
Arbitrary number of arguments
def cheeseshop(kind, *arguments, **keywords): print("-- Do you have any", kind, "?") print("-- I'm sorry, we're all out of", kind) for arg in arguments:
print(arg) print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
• An arbitrary number of arguments can also be passed as a dictionary. This is denoted by the ** preceding the parameter’s name.
• This can also be combined with the the arbitrary number of arguments passed as a tuple, where a single * precedes the parameter’s name.
© 2019 Ritwik Banerjee 50
Arbitrary number of arguments
def cheeseshop(kind, *arguments, **keywords): print("-- Do you have any", kind, "?") print("-- I'm sorry, we're all out of", kind) for arg in arguments:
print(arg) print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
© 2019 Ritwik Banerjee 51
Function annotations
• Python is dynamically typed, but it allows the us to include type hints in the form of annotations:
def sum_two(a: int, b: int) -> int:
return a + b
• Syntax
• The return type’s annotation is written after the
function signature by adding ->
• The annotations are not mandatory and have no effect on the program’s semantics. They are merely for the programmer’s ease of understanding.
• Theannotationsarestoredinthe__annotations__ attribute of the function, which is a dictionary.
>>> sum_two.annotations__
{‘a’:
Python coding conventions
• Python programming guidelines and conventions are defined in Python Enhancement Proposals, or PEPs.
• PEP 8 has emerged as the nearly- universal style doctrine.
• Fortunately, most modern IDEs (e.g., PyCharm) abide by PEP 8 and will warn the programmer of any violations.
• For this course, we are not enforcing the style guide in its entirety, but we do require some minimal adherence, as described in this basic coding style guideline.
© 2019 Ritwik Banerjee 53
Organizing your Python code
• The interactive shell is good for a quick check of a few lines of code.
• For your code to last longer than one session, you will be writing your code in a .py file. This is commonly called a python script.
• Longer programs are split across multiple files, of course.
• Easier maintenance • Code reusability
• The proper way to do this is to put definitions in file(s), and use them in a script (or the shell). Such a file is called a module.
• Definitions from a module can
be imported into other modules.
© 2019 Ritwik Banerjee 54
Modules
def fib(n): # write Fibonacci series up to n a, b = 0, 1
while a < n: print(a, end=' ') a, b = b, a+b
print()
def fib2(n): # return Fibonacci series up to n result = []
a, b = 0, 1 while a < n:
result.append(a)
a, b = b, a+b return result
• Amoduleisa.pyfile containing definitions and functions.
• The module’s name is the file name, minus the .py extension.
• Within a module, this name is available as the built-in global variable __name__.
© 2019 Ritwik Banerjee
55
Modules
>>> import fibonacci
>>> fibonacci.fib(120)
0 1 1 2 3 5 8 13 21 34 55 89 >>> fibonacci.__name__ ‘fibonacci’
>>> # assign a local name >>> fib = fibonacci.fib
>>> fib(120)
0 1 1 2 3 5 8 13 21 34 55 89
• Amoduleisa.pyfile containing definitions and functions.
• The module’s name is the file name, minus the .py extension.
• Within a module, this name is available as the built-in global variable __name__.
© 2019 Ritwik Banerjee
56
How to import?
1.import fibonacci
• Use
2.from fibonacci import fib, fib2
• Useitemnamedirectlyinsubsequentcode. • Butmodulenameisnotdefinedinthiscode.
3. from fibonacci import *
• Importallnamesexceptthosebeginningwithan underscore.
It introduces an unknown set of names into the interpreter, possibly hiding some things you have already defined.
Code is less readable (due to the above reason).
4.import fibonacci as fbn
• Directlybindaneasy-to-useshorternametothe module.
Running a module
if __name__ == “__main__”: import sys
fibonacci(int(sys.argv[1]))
• By adding this code at the end of our module, we can use it as a script as well as an importable module.
• Because the code that parses the command line only runs if the module is executed as the __main__ module.
• Simply importing the module will not execute the code.
• We need to run the module as python fibonacci.py
© 2019 Ritwik Banerjee
58
Parameter passing
in Python
© 2019 Ritwik Banerjee
59
Parameters or arguments?
Strictly speaking, parameters are inside functions or procedures, while arguments are used in procedure calls, i.e., the values passed to the function at run-time.
A useful function cannot operate in complete isolation from its environment.
Parameters provide the mechanism to use objects from “outside” a function, within the function.
But people often use ‘parameter’ and ‘argument’ interchangeably.
© 2019 Ritwik Banerjee
60
Recap: types
of parameter
passing
• The classical distinction between two ways of passing parameters has always been in terms of pass-by-value or pass-by-reference.
• They are also known as call-by-value and call-by-reference, respectively.
• This distinction, however, is becoming increasingly obsolete because the original pass- by-reference technique is no longer favored by newer languages.
Recap: types of parameter passing
In pass-by-value, the argument expression is evaluated, and the result of the evaluation is bound to the corresponding variable in the function.
• That is, if the expression is a variable, its value will be copied to the corresponding parameter.
• The variable in the caller’s scope will be unchanged when the function returns.
• Used in C and C++.
In pass-by-reference, function gets a reference to the argument, rather than a copy of its value.
• The value of the variable in the caller’s scope can be changed by the function being called.
• Saves time and space in computation.
• C++ supports this. Some languages (e.g., Perl) use this as default.
© 2019 Ritwik Banerjee 62
class Main {
int value = 0;
static void accept_reference(Main e) {
e.value++; // will change the referenced object e = null; // will only change the parameter
}
static void accept_primitive(int v) { v++; } // will only change the parameter public static void main(String… args) { … // (cont. in next slide)
© 2019 Ritwik Banerjee 63
A Java example
int value = 0;
Main ref = new Main(); // reference
// what we pass is the reference, not the object.
// the reference is copied (pass-“reference”-by-value)
accept_reference(ref);
assert ref != null && ref.value == 1;
// the primitive int variable is copied
accept_primitive(value);
assert value == 0;
}
}
© 2019 Ritwik Banerjee 64
A Java example
Value types and reference types
Recall that data types can be categorized into value types and reference types. The names are self-explanatory:
Some languages like Java use a hybrid approach:
Python
• A value type is an actual value.
• A reference type is a reference to another value.
• primitives are values types
• everything else is a reference type
• Everything is a reference type.
© 2019 Ritwik Banerjee 65
Parameter passing in Python
• Python is pass-by-value.
But remember that since everything is a reference type, the reference is passed by
value!.
• When a parameter is passed to a function, the object reference can’t be changed from within the called function.
• If the argument itself is mutable, e.g., a list, then there are two scenarios:
1. The elements of the list can be changed in place. These changes are not changing the
reference to the list, and therefore, will be there in the caller’s scope.
2. If a new list is assigned to the same name, the old list will not be affected, i.e., the list in the caller’s scope will remain unmodified.
© 2019 Ritwik Banerjee 66
Observing parameter passing by using id()
def ref_demo(x):
print(“x=”,x,” id=”,id(x))
x=42
print(“x=”,x,” id=”,id(x))
© 2019 Ritwik Banerjee
67
Object-oriented
in Python
programming
© 2019 Ritwik Banerjee
68
Names and objects
• Recall aliasing: multiple names (in multiple scopes … we are getting there soon) can be bound to the same object.
• Aliasing can usually be ignored when dealing with immutable objects like numbers, strings, tuples, etc.
• For mutable objects (e.g., lists and dictionaries), however, aliasing offers surprising benefits.
• Aliasing behaves a bit like pointers:
• it is cheap to pass an object, since only a pointer
is passed by the implementation.
• if a function modifies an object passed as an argument, the modification can be seen by the caller.
Namespaces
• A namespace is a mapping from names to objects.
• Behind the scenes, namespaces are implemented as dictionaries
• this is usually not noticeable to the programmer, and
• this implementation detail may change in future versions of Python.
• Examples of namespaces:
• built-in function and exception names (e.g., abs(),
KeyError)
• global names in a module
• local names in a function
There is no relation between names in different namespaces.
• Two different modules may both define a function with the same name.
• The programmer must use the module’s name as a prefix to use the correct function.
Namespaces
• Namespacesarecreatedatdifferenttimes(recallhow binding of names to objects happens at different times):
• Thenamespaceofabuilt-innameiscreatedwhenthe Python interpreter starts; this is never deleted (these names live in a module called builtins)
• Theglobalnamespaceforamoduleiscreatedwhenthe module definition is read
• Unlessspecificallydeleted,amodulenamespaceexists until the interpreter quits.
• Thestatementsexecutedatthetop-levelareconsidered part of a module called __main__.
• __main__ is the __name__ of the top-level module.
• Thisiswhyweincludedthecodeif__name__==
“__main__”: … to run a module directly.
• Sincethismoduleisreadassoonastheexecution starts, the statements executed in this module have their own global namespace throughout.
Attributes
• Amodule’sattributesandthesetofglobalnamesdefinedin the module share the exact same namespace. In this sense, the complete set of attributes of an object defines a namespace.
• Anattributecanberead-only,orwriteable.
• UnlikeJava,Pythonallowsyoutoeasilydeleteanattribute
(removes the attribute from the namespace) as follows:
del
• InPython,anattributeusuallyindicatesanynamethat follows a dot
• Inp.name,thefieldnameisanattributeoftheobjectp. • Infibonacci.fib,thefunctionfibisanattributeof
the module fibonacci.
Scope
• If there is a function func() in a module m inside a package x.y.z, then x.y.z.m.func is its fully qualified reference/name, while just func is its unqualified name/reference.
• A scope is a textual region of a program where a namespace is accessible through unqualified reference.
• It is determined statically, but used dynamically.
• During execution, there are multiple nested scopes whose
namespaces are directly accessible (in order of search):
• the innermost scope containing local names
• the scopes of enclosing functions
• the scope containing the current module’s global names
• the outermost scope, which is the namespace containing built-in names
Static view of scopes
Global (module): names bound at the top level of a module or explicitly defined as global in a function
Built-in (python): predefined bindings like len, int, str, etc.
Local (function): names bound inside a function def
© 2019 Ritwik Banerjee
74
Dynamic use of scopes
4321
built-in
• finally, the built-in bindings of Python are searched
global namespace
• the module’s global namespace is searched next for the binding
enclosing functions
• the scopes of enclosing functions, starting the nearest enclosing scope, are searched next
• these are bindings that are non-local but also non-global
local
• the innermost scope containing local bindings is searched first
© 2019 Ritwik Banerjee
75
© 2019 Ritwik Banerjee
76
If a name is declared global, then all references and assignments go directly to the middle scope containing the module’s global names.
The nonlocal statement is used to re-bind a name to a value in an outer (but still non-global) namespace. This is very useful is there are multiple levels of nesting between a function definition and the module
Specifying namespaces
Why Python does what it does with scopes and namespaces
• In many languages, variables are treated as global if not declared otherwise.
• Python has the opposite approach. Why?
• Because global variables should be used only when absolutely necessary.
• In most cases, if you are tempted to use a global variable, it is better to (i) have a formal parameter to get a value into a function, or (ii) return a value to get it out of a function.
• Variables inside a function are local to this function by default.
• The function body is the scope of such a variable.
• Remember: a variable can’t be declared in the way they are declared in programming languages like Java.
• In Python, they are declared by defining them, i.e., the first time you assign a value to a variable.
• When declared, it automatically the data type of the object which has to be assigned to it.
© 2019 Ritwik Banerjee 77
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)
do_nonlocal()
print(“After nonlocal assignment:”, spam)
do_global()
print(“After global assignment:”, spam)
scope_test()
print(“In global scope:”, spam)
Example taken from https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces © 2019 Ritwik Banerjee 78
• Scenario 1: before calling the function f(), there is no local variable s (i.e., no object is bound to the name).
• So, the value of the global variable is used.
• What will happen if we change the value of the variable inside the function?
# Scenario 1: def f():
print(s)
s = “Everyone loves Python!”
f()
# Scenario 2: def f():
s = “Everyone loves Python!”
print(s)
s = “But why?” f()
print(s)
© 2019 Ritwik Banerjee
79
# Scenario 1: throws error because in line 2, the variable ‘s’ is referenced before assignment
# Scenario 2: the global designation indicates that in the local namespace of the function f(), the name ‘s’ is being borrowed from the global namespace of the module.
def f(): global s
print(s)
s = “Everyone loves Python!” print(s)
s = “But why?”
f()
print(s) # what will this print?
def f():
print(s) # line 2
s = “Everyone loves Python!” print(s)
s = “But why?” f()
print(s)
© 2019 Ritwik Banerjee 80
© 2019 Ritwik Banerjee
81
• Theglobalstatementinsideofthe nested function does not affect the variable x in the function f (x=42).
• Aftercallingf()avariablexexistsin the module’s global namespace and has the value 43.
• Conclusion (again): a variable defined inside of a function is local unless it is explicitly marked as global.
• If we replace the line global x inside g() with nonlocal x, then the value of the variable x in the function f() changes because of the update inside the inner function g().
• The variable x does not, however, exist in the global namespace. So, the last line in this code will now throw an error:
NameError: name ‘x’ is not defined
© 2019 Ritwik Banerjee 82
First-class everything
• Python was designed according to the principle “first-class everything”.
“One of my goals for Python was to make it so that all objects were ‘first class’. By this, I meant that I wanted all objects that could be named in the language (e.g., integers, strings, functions, classes, modules, methods, and so on) to have equal status. That is, they can be assigned to variables, placed in lists, stored in dictionaries, passed as arguments, and so forth.”
~ Guido van Rossum
© 2019 Ritwik Banerjee 83
Topics in Python OOP
Classes
Encapsulation
Magic methods
Class and Instance attributes
Properties, getters, setters
Multiple Inheritance
Operator Overloading
Class and Type
Metaclasses and Abstract classes
© 2019 Ritwik Banerjee 84
Classes
• Classes provide a means of bundling data and functionality together.
• Creating a new class creates a new type of object, allowing new instances of that type to be made.
• Each class instance can have attributes attached to it for maintaining its state.
• Class instances can also have methods for modifying its state.
• Compared with most other programming languages, Python’s class mechanism adds classes with a minimum of new syntax and semantics.
A minimal class in Python
class Robot:
pass
• A class has two parts: a header, and a body. • We can already start using this class:
class Robot:
pass
if __name__ == “__main__”:
x = Robot()
y = Robot()
y2 = y
print(y == y2) # True
print(y == x) # False
© 2019 Ritwik Banerjee 86
Encapsulation
• That is, there is no restriction on accessing a variable or calling a method in a Python program.
• All member variables and methods are public by default in Python!
• So, to make a member public, you do no extra work:
class Cup:
def __init__(self):
self.color = None
self.content = None
def fill(self, beverage):
self.content = beverage
def empty(self):
self.content = None
Encapsulation
• Having a member protected or private is a matter of convention in Python.
• Protected members are prefixed with a single underscore (e.g., _content)
class Cup:
def __init__(self):
self.color = None
self._content = None
def fill(self, beverage):
self._content = beverage
def empty(self):
self._content = None
• You can still access the variable from outside the class and assign a value: cup._content = “tea”
Encapsulation
• Having a member protected or private is a matter of convention in Python.
• Private members are prefixed with two underscores (e.g., __content). They can still be accessed from outside; the double underscore simply means that they should not be
accessed or modified from outside.
class Cup:
def __init__(self):
self._color = None. # protected
self.__content = None # private
def fill(self, beverage):
self.__content = beverage
def empty(self):
self.__content = None
Magic methods
• These are methods with fixed names that serve pre-specified purposes.
• Python examples: __init__(), __str__(), __add__().
• Java examples: equals(), toString(), hashCode().
• You don’t always have to call them directly, since they are invoked behind the scene for those pre-specified purposes.
• In Java, for example, the programmer-defined equals() method in, say, MyClass, gets used to check whether an element already exists in a Set
Magic methods: constructors & string representations
• The__init__methodisusedtoinitializeaninstance.
• The__str__methodisusedtoprintaninstance.
• Thisiswhyprint()ingapersonshowsupdifferently compared to simply viewing the object.
class Person:
def __init__(self, firstname, lastname=None):
self.firstname = firstname self.lastname = lastname
def __str__(self):
return self.firstname + ” ” + self.lastname
© 2019 Ritwik Banerjee 91
• Inadditionto__str__,Pythonalsousesanother form of string representation.
• An object’s internal representation for the Python interpreter is done using __repr__.
• We can get the original object back from the __repr__ string.
• Similar to the notion of serializable in Java, but unlike Java, __repr__ is easily interpretable by humans as well.
Magic methods:
reversible/retrievable
string representation
© 2019 Ritwik Banerjee 92
class Person:
def __init__(self, firstname, lastname=None):
self.firstname = firstname self.lastname = lastname
def __str__(self):
return self.firstname + ” ” + self.lastname
def __repr__(self):
return “Person(\”” + self.firstname +
“\”, \”” + self.lastname + “\”)”
Instance and Class attributes
• As in Java, Python too has attributes whose value depends on the specific instance. These are called instance attributes.
• Otherwise, an attribute can have a value that is independent of any specific instance. These are called class attributes.
class Person:
ssn = ‘This is a class attribute.’ …
© 2019 Ritwik Banerjee 93
Class attributes
• Owned by the class.
• Shared by all instances of the class. Therefore, they have the same value for all instances of that class.
• Read-access can be done via an instance name (valid, but not recommended) or the class name.
• But to modify, the access must be via the class. Otherwise, you end up creating a new instance variable.
© 2019 Ritwik Banerjee 94
Class attributes
• Owned by the class.
• Shared by all instances of the class. Therefore, they have the same value for all instances of that class.
• Read-access can be done via an instance name (valid, but not recommended) or the class name.
• But to modify, the access must be via the class. Otherwise, you end up creating a new instance variable.
• Internally, the class and instance attributes are stored in separate dictionaries
© 2019 Ritwik Banerjee
95
96
class Person:
ssn = ‘This is a class attribute.’
# __init__ etc.
@staticmethod
def get_ssn(): return Person.ssn
Static methods
• Since class attributes are not instance-specific, instance methods (remember, methods are attributes too) should not be the ones dealing with manipulating them.
• The correct approach is to use static methods, where we can call via the class name or via the instance name without the necessity of passing a reference to an instance to it.
© 2019 Ritwik Banerjee
Class methods
• Python has a somewhat confusing terminology/concept for Java programmers: class methods. These are not tied to instances, but they are not static either!
• In Python, static methods are not bound to a class.
• For class methods, the first parameter is a reference
to a class (i.e., a class object).
• They are often used when we need one static method to call other static methods.
• Very helpful in various design patterns like the decorator pattern and the factory pattern. (… CSE 316 stuff that we will not cover).
Class methods
class fraction(object):
def __init__(self, n, d):
self.numerator, self.denominator = fraction.reduce(n, d)
@staticmethod
def gcd(a,b): while b != 0:
a, b = b, a%b return a
@classmethod
def reduce(cls, n1, n2):
g = cls.gcd(n1, n2) return (n1 // g, n2 // g)
def __str__(self): return
str(self.numerator)+’/’+str(self.denominator)
f = fraction(8,24)
print(f) # prints 1/3, not 8/24
# Note: // is for integer division in # Python 3, and / is for floating-point # division.
© 2019 Ritwik Banerjee 98
Class methods and inheritance
class Pets:
name = “pet animals”
@staticmethod
def about():
print(“Class of {}!”.format(Pets.name))
# Dogs is a child class of Pets
class Dogs(Pets):
name = “my best friends!”
# Cats is a child class of Pets
class Cats(Pets): name = “cats.”
p = Pets()
d = Dogs()
c = Cats()
p.about() # prints “Class of pet animals!” d.about() # prints “Class of pet animals!”
c.about() # prints “Class of pet animals!”
© 2019 Ritwik Banerjee 99
Class methods and inheritance
# We could do the following
class Pets:
name = “pet animals”
@staticmethod
def about():
print(“This class is about {}!”.format(Pets.name))
class Dogs(Pets):
name = “my best friend!”
@staticmethod
def about():
print(“This class is about {}!”.format(Dogs.name))
class Cats(Pets):
name = “cats”
@staticmethod
def about():
print(“This class is about {}!”.format(Cats.name))
© 2019 Ritwik Banerjee 100
Class methods and inheritance
• But by using a class method, we can avoid rewriting code for all subclasses.
• This is done by passing the class as a parameter.
• The first parameter of a class method is always a class, indicated by cls (just how the first parameter of an instance method is always an instance, indicated by self).
class Pets:
name = “pet animals”
@classmethod
def about(cls):
print(“This class is about {}!”.format(cls.name))
class Dogs(Pets):
name = “my best friend!”
class Cats(Pets):
name = “cats”
© 2019 Ritwik Banerjee 101
Topics in Python OOP
Classes
Encapsulation
Magic methods
Class and Instance attributes
Properties, getters, setters
Multiple Inheritance
Operator Overloading
Class and Type
Metaclasses and Abstract classes
© 2019 Ritwik Banerjee 102
Properties, getters, and setters
• The “Java way” to read and write attributes is to use getters and setters.
• This leads to Python code of the form obj2.set_x(obj1.get_x() +
obj2.get_y())
• Of course, it’s nicer and easier to read/write code that looks like
obj2.x = obj1.x + obj2.y
• This is the “Python way”.
• It has no encapsulation!
• The lack of encapsulation can cause a lot of trouble if we want to change the implementation in the future. So let’s see what can potentially go wrong.
© 2019 Ritwik Banerjee 103
Properties, getters, and setters
self.__x = 0 elif x > 1000:
class P:
def __init__(self, x):
self.set_x(x) def get_x(self):
return self.__x
def set_x(self, x): if x < 0:
self.__x = 1000 else:self.__x = x
• The class P has an attribute x that can take values between 0 and 1000.
• The current setter limits illegal values within this range.
• But what if the class has these attributes as public, and has no methods (i.e., x is set through __init__, and there’s nothing else).
• People may have already written code like: p = P(4)
p.x = 1001
© 2019 Ritwik Banerjee 104
Properties, getters, and setters
self.__x = 0 elif x > 1000:
class P:
def __init__(self, x):
self.set_x(x) def get_x(self):
return self.__x
def set_x(self, x): if x < 0:
self.__x = 1000 else:self.__x = x
• The class shown here breaks the previous contract, since the attribute x is not available any more.
• This is why encapsulation is important in languages like Java.
• It is recommended to use only private attributes with getters and setters. Then, the internal implementation can be changed without having to change the interface.
• Python offers a different solution to this problem by using the decorator @property.
© 2019 Ritwik Banerjee 105
Properties, getters, and setters
• A method used to get an attribute value is decorated with @property.
• A method used to set an attribute value is decorated with @attribute_name.setter.
• Note that the initial setting is happening inside the constructor. The setter method is used to check the validity of the value being assigned.
• We have two methods with the same name but different parameters.
• This is usually not allowed in Python, since Python offers optional parameters to get around the need for this.
• It works here due to the properties.
class P:
def __init__(self, x):
self.x = x
@property
def x(self):
return self.__x
@x.setter
def x(self, x): if x < 0:
self.__x = 0 elif x > 1000:
self.__x = 1000 else:
self.__x = x
© 2019 Ritwik Banerjee
106
Inheritance hierarchy
Vehicle
Truck
superclass
direct subclasses
Car
Bus
Bike
Convertible
Vans
Heavy Truck
Medium Truck
Motorbike
Pedalbike
© 2019 Ritwik Banerjee
107
Inheritance hierarchy
• Syntax:
class DerivedClassName(BaseClassName):
… # indented block with class implementation
© 2019 Ritwik Banerjee 108
Inheritance hierarchy
class Person:
def __init__(self, first, last):
self.firstname = first
self.lastname = last
def name(self):
return self.firstname + ” ” + self.lastname
class Employee(Person):
def __init__(self, first, last, staffnum):
Person.__init__(self, first, last) # equivalently, super().__init__() self.staffnumber = staffnum
def get_employee(self):
return self.name() + “, ” + self.staffnumber
x = Person(“Marge”, “Simpson”)
y = Employee(“Homer”, “Simpson”, “1007”) print(x.name())
print(y.get_employee())
© 2019 Ritwik Banerjee 109
Inheritance hierarchy
class Person:
def __init__(self, first, last):
self.firstname = first
self.lastname = last
def name(self):
return self.firstname + ” ” + self.lastname
class Employee(Person):
def __init__(self, first, last, staffnum):
Person.__init__(self, first, last) # equivalently, sup
self.staffnumber = staffnum
def get_employee(self):
return self.name() + “, ” + self.staffnumber
x = Person(“Marge”, “Simpson”)
y = Employee(“Homer”, “Simpson”, “1007”) print(x.name())
print(y.get_employee())
© 2019 Ritwik Banerjee 110
er().__init__()
• These two methods only offer string representations, and due to the inheritance hierarchy, we are reusing name() in the get_employee() method.
• So, can we instead use overriding and write cleaner code?
Overriding
• Let’s add an age attribute and use __str__() to modify do just that:
class Person:
def __init__(self, first, last, age):
self.firstname = first
self.lastname = last
self.age = age
def __str__(self): # overrides builtins.object.__str__()
return self.firstname + ” ” + self.lastname + “, ” + str(self.age)
class Employee(Person):
# overrides Person.__init__()
def __init__(self, first, last, age, staffnum):
super().__init__(first, last, age)
self.staffnumber = staffnum
def __str__(self): # overrides Person.__str__() return super().__str__() + “, ” + self.staffnumber
© 2019 Ritwik Banerjee 111
• If you look at the common method overloading scenarios in Java, you will see that they are done because (i) we want default values for some argument, and (ii) we want different types for the function parameters.
• Python allows extreme flexibility in defining function arguments by means of required arguments, optional arguments with default values, and variadic functions. This takes care of the first reason.
• Python is dynamically typed, so there is no room or need to define multiple functions with the same name but different data types for its arguments.
• For this reason, there is no separate need for method overloading.
112
Operator overloading
© 2019 Ritwik Banerjee
• A class can define operations by defining specific magic methods. This leads to operator overloading, i.e., the same operator can mean different things in the context of different classes.
• Perhaps the most commonly used overloaded operator is plus. It is implemented using the __add__() magic method.
• What does
• If
addition.
• If
113
Operator overloading
© 2019 Ritwik Banerjee
Operator overloading
• Of course, we can’t mix-n-match two data types for a single operator.
• The meaning of an operator is fixed once Python interprets the type of the first argument.
• Sincex+yisreallyx.__add__(y),itisthe type of x that defines what the method argument y can be.
• Note the error message in this example. It shows that Python is expecting the type of the second argument to be str and not the type of the first argument to be a number, even though the programmer clearly had numbers in mind when implementing the method.
© 2019 Ritwik Banerjee 114
Multiple Inheritance
Image Credit: https://www.python-course.eu/python3_multiple_inheritance.php
© 2019 Ritwik Banerjee 115
Multiple Inheritance
class Base1: pass
class Base2: pass
class Derived(Base1, Base2): pass
The diamond problem and its resolution
class A:
def m(self):
print(“m of A called”)
class B(A):
def m(self):
print(“m of B called”)
class C(A):
def m(self):
print(“m of C called”) class D(B, C): pass
x = D()
x.m() # prints: m of B called
© 2019 Ritwik Banerjee 117
The diamond problem and its resolution
• How does Python resolve the conflicting options arising from multiple inheritance of classes?
• It uses what is called a linearization algorithm: the tree structure from multiple inheritance is broken down into a linear order.
• Python uses a depth-first approach along the inheritance tree.
• This is the method resolution order (MRO).
• Different versions of Python have used different orders for resolution of the diamond problem.
For this course, we will not go beyond using a simple depth-first search.