Python 2
Errors and Exceptions¶
Python supports exception handling. Errors and exceptions occur when an unexpected event occurs. For example, SyntaxError
# Uncomment and try this. This will cause an error. Pay attention to the error message.
# while True print('Hello world')Exceptions¶
Exceptions break the “normal flow” of the program and propogates an exception until it is catched. If an exception is not catched it causes the program to exit. A few examples
# Uncomment and try this. This will cause error. Pay attention to the error message.
# 10 * (1/0)# Uncomment and try this. This will cause error. Pay attention to the error message.
# 4 + spam*3# Uncomment and try this. This will cause error. Pay attention to the error message.
# '2' + 2The error types in the example are ZeroDivisionError, NameError and TypeError.
Handling exceptions¶
To catch errors or exception try catch block is used
while True:
try:
x = int(input("Please enter a number: "))
break
except ValueError as err:
print(f"You entered something that is not a number. Error Type: {type(err)}; Error args: {err.args}" )
The try statement works as follows.
First, the try clause (the statement(s) between the try and except keywords) is executed.
If no exception occurs, the except clause is skipped and execution of the try statement is finished.
If an exception occurs during execution of the try clause, the rest of the clause is skipped. Then, if its type matches the exception named after the except keyword, the except clause is executed, and then execution continues after the try/except block.
If an exception occurs which does not match the exception named in the except clause, it is passed on to outer try statements; if no handler is found, it is an unhandled exception and execution stops with a message as shown above.
A try statement may have more than one except clause, to specify handlers for different exceptions. At most one handler will be executed. Handlers only handle exceptions that occur in the corresponding try clause, not in other handlers of the same try statement.
Raising Exceptions¶
The raise statement allows the programmer to force a specified exception to occur. For example:
# Uncomment and try this:
# raise NameError('HiThere')The sole argument to raise indicates the exception to be raised. This must be either an exception instance or an exception class (a class that derives from BaseException, such as Exception or one of its subclasses). If an exception class is passed, it will be implicitly instantiated by calling its constructor with no arguments.
If you need to determine whether an exception was raised but don’t intend to handle it, a simpler form of the raise statement allows you to re-raise the exception:
# Uncomment "raise" and try this:
try:
raise NameError('HiThere')
except NameError:
print('An exception flew by!')
# raise
Predefined Clean-up Actions¶
Some objects define standard clean-up actions to be undertaken when the object is no longer needed, regardless of whether or not the operation using the object succeeded or failed. Look at the following example, which tries to open a file and print its contents to the screen.
%%writefile myfile.txt
he Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!for line in open("myfile.txt"):
print(line, end="")The problem with this code is that it leaves the file open for an indeterminate amount of time after this part of the code has finished executing. This is not an issue in simple scripts, but can be a problem for larger applications. The with statement allows objects like files to be used in a way that ensures they are always cleaned up promptly and correctly.
with open("myfile.txt") as f:
for line in f:
print(line, end="")Python Data Model¶
Objects are Python’s abstraction for data and code. Every object has an identity, a type and a value. An object’s type determines the operations that the object supports (e.g., “does it have a length?”) and also defines the possible values for objects of that type. The type() function returns an object’s type (which is an object itself).
Objects are never explicitly destroyed; however, when they become unreachable they may be garbage-collected. An implementation is allowed to postpone garbage collection or omit it altogether — it is a matter of implementation quality how garbage collection is implemented, as long as no objects are collected that are still reachable.
Python operator overloading or special methods¶
object.__len__(self)¶
Called to implement the built-in function len(). Should return the length of the object, an integer >= 0. Also, an object that doesn’t define a __bool__() method and whose __len__() method returns zero is considered to be false in a Boolean context.class Person:
def __init__(self, name, height):
self.name = name
self.height = height
def __len__(self):
return self.height
bradpitt = Person("Brad Pitt", 170)
print(len(bradpitt))Emulating numeric types¶
object.__add__(self, other)object.__sub__(self, other)object.__mul__(self, other)object.__matmul__(self, other)object.__truediv__(self, other)object.__floordiv__(self, other)object.__mod__(self, other)object.__divmod__(self, other)object.__pow__(self, other[, modulo])object.__eq__(self, other)object.__str__(self)object.__repr__(self)
These methods are called to implement the binary arithmetic operations (+, -, *, @, /, //, %, divmod(), **, ==, str(), repr()). For instance, to evaluate the expression x + y, where x is an instance of a class that has an add() method, type(x).add(x, y) is called. The divmod() method should be the equivalent to using floordiv() and mod(); it should not be related to truediv().
class Matrix:
""" A matrix class that emulates 2D matrices"""
def __init__(self, listoflists):
self.nums = (listoflists.nums
if isinstance(listoflists, Matrix)
else listoflists)
self.rows = len(self.nums)
self.cols = len(self.nums[0])
if not all(len(r) == self.cols for r in self.nums):
raise ValueError(f"Invalid matrix: {self.nums}")
def __add__(self, other):
""" Add two matrices: self + other"""
other = Matrix(other)
if not (self.rows == other.rows and self.cols == other.cols):
raise ValueError("Incompatible addition")
return Matrix([
[mij + omij
for mij, omij in zip(mi, omi)]
for mi, omi in zip(self.nums, other.nums)])
A = Matrix([[1, 2, 3],
[4, 5, 6]])
B = Matrix([[1, 3, 5],
[2, 4, 6]])
A + B# To see the matrix you have to acess the nums. This not good.
(A + B).numsYou can override how the str() and repr() functions work by overriding __str__ and __repr__.
oldMatrix = Matrix # Rename Matrix to something else
# Inherit the old matrix so that we do not have to
# reimplement the methods. This is a dirty hack.
# DONOT do this in real world.
class Matrix(oldMatrix):
def __str__(self):
""" Convert the matrix into a string """
return ("[[" +
"],\n[".join([", ".join(map(str, r)) for r in self.nums]) +
"]]")
# Representation for this class can be same as the stringify
# function
__repr__ = __str__
A = Matrix([[1, 2, 3],
[4, 5, 6]])
B = Matrix([[1, 3, 5],
[2, 4, 6]])
A + BExercise 1¶
We also added a transpose (T) method and __eq__ method to the Matrix which overrides == comparison operator. Now you have to add a matrix multiplication method to the Matrix class.
oldMatrix = Matrix # Rename Matrix to something else
# Inherit the old matrix so that we do not have to
# reimplement the methods. This is a dirty hack.
# DONOT do this in real world.
class Matrix(oldMatrix):
def T(self):
"""Transpose a matrix"""
return list(zip(*self.nums))
def __eq__(self, other):
"""Test to matrices for equality"""
other = Matrix(other)
return ( other.rows == self.rows
and other.cols == self.cols
and all(mi == omi
for mi, omi in zip(self.nums, other.nums)))
# YOUR CODE HERE
raise NotImplementedError()A = Matrix([[1, 2, 3],
[4, 5, 6]])
B = Matrix([[1, 3, 5],
[2, 4, 6]])
assert A @ B.T() == [[22, 28],
[49, 64]]
class Range:
def __init__(self, start=0, end=10000, step=1):
self.start = start
self.end = end
self.step = step
self.x = start
def __iter__(self):
return self
def __next__(self):
if self.x < self.end:
x = self.x
self.x += 1
return x
else:
raise StopIteration()
for i in Range(0, 10):
print(i)
The main lesson is that Python is flexible and objects are self-aware. If you want your class to behave like in-built objects, there is probably a way to do that.
Scopes and Namespaces¶
A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries, but that’s normally not noticeable in any way (except for performance), and it may change in the future. Examples of namespaces are: the set of built-in names (containing functions such as abs(), and built-in exception names); the global names in a module; and the local names in a function invocation. In a sense the set of attributes of an object also form a namespace. The important thing to know about namespaces is that there is absolutely no relation between names in different namespaces; for instance, two different modules may both define a function maximize without confusion — users of the modules must prefix it with the module name.
A scope is a textual region of a Python program where a namespace is directly accessible. “Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace.
Although scopes are determined statically, they are used dynamically. At any time during execution, there are 3 or 4 nested scopes whose namespaces are directly accessible:
the innermost scope, which is searched first, contains the local names
the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contain non-local, but also non-global names
the next-to-last scope contains the current module’s global names
the outermost scope (searched last) is the namespace containing built-in names
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)Instance objects¶
Data attributes correspond to to “data members” in C++. Data attributes need not be declared; like local variables, they spring into existence when they are first assigned to. For example, if x is the instance of MyClass created above, the following piece of code will print the value 16, without leaving a trace:
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i)
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counterThe other kind of instance attribute reference is a method. A method is a function that “belongs to” an object. (In Python, the term method is not unique to class instances: other object types can have methods as well. For example, list objects have methods called append, insert, remove, sort, and so on. However, in the following discussion, we’ll use the term method exclusively to mean methods of class instance objects, unless explicitly stated otherwise.)
class MyClass:
"""A simple example class"""
def __init__(self, i=12345):
self.i = i
def f(self):
return f'hello world from {type(self).__name__}(i={self.i:d})'
x = MyClass(i=20)
xf = x.f
for _ in range(10):
print(xf())Exercise 2¶
Modify SomeClass below so that it has a method f using outside_function which behaves exactly as MyClass above without redefining the function contents.
def outside_function(self):
return f'hello world from {type(self).__name__}(i={self.i:d})'
class SomeClass:
"""A simple example class"""
def __init__(self, i=12345):
self.i = i
# YOUR CODE HERE
raise NotImplementedError()assert SomeClass(30).f() == 'hello world from SomeClass(i=30)'
assert SomeClass.f is outside_function # is keyword checks if id(obj) == id(obj2)
assert SomeClass(30).f.__func__ is outside_function # is keyword checks if id(obj) == id(obj2)