# Object Oriented Programming (II)¶





# Classes have a bunch of special methods¶

the mirror of __init__ is __del__ (it is the tear down during clean up)

In [ ]:
class Bear:
def __init__(self,name):
self.name = name
print(" made a bear called %s" % (name))
def __del__(self):
print("Bang! %s is no longer." % self.name)

In [ ]:
y = Bear("Yogi") ; c = Bear("Cal")

In [ ]:
del y; del c

In [ ]:
## note that I'm assigning y twice here
y = Bear("Yogi") ; y = Bear("Cal")


If we quit (not from the notebook but the interpreter or ipython)

>>> f = Bear("Fuzzy")
made a bear called Fuzzy
>>> exit()
Bang! Fuzzy is no longer.
BootCamp>

neither __init__ or __del__ are allowed to return anything
In [ ]:
%%file bear.py
import datetime
class Bear:
logfile_name = "bear.log"
bear_num     = 0
all_bear_names = []
def __init__(self,name):
self.name = name
print((" made a bear called %s" % (name)))
self.logf  = open(Bear.logfile_name,"a")
Bear.bear_num += 1
self.my_num = Bear.bear_num
self.logf.write("[%s] created bear #%i named %s\n" % \
(datetime.datetime.now(),Bear.bear_num,self.name))
self.logf.flush()

def growl(self,nbeep=5):
print(("\a"*nbeep))

def __del__(self):
print(("Bang! %s is no longer." % self.name))
self.logf.write("[%s] deleted bear #%i named %s\n" % \
(datetime.datetime.now(),self.my_num,self.name))
self.logf.flush()
# decrement the number of bears in the population
Bear.bear_num -= 1
# dont really need to close because Python will do the garbage collection
#  for us. but it cannot hurt to be graceful here.
self.logf.close()

def __str__(self):
return " name = %s bear number = %i (population %i)" % \
(self.name, self.my_num,Bear.bear_num)

In [ ]:
!rm bear.log
%run bear

In [ ]:
a = Bear("Yogi")

In [ ]:
b = Bear("Yogi")

In [ ]:
b = a

In [ ]:
b = Bear("Fuzzy")

In [ ]:
Bear.bear_num

In [ ]:
del a; del b

In [ ]:
Bear.bear_num

In [ ]:
!cat bear.log


### Classes have a bunch of special methods¶

__str__ is a method that defines how a Class should represent itself as a string

it takes only self as an arg, must return a string

In [ ]:
run bear

In [ ]:
b = Bear("Fuzzy")

In [ ]:
print(b)

In [ ]:
a = Bear("Yogi")

In [ ]:
print(a)


this is the kind of formatting that datetime() is doing in it's own __str__

In [ ]:
%%file bear2.py
import datetime
class Bear:
logfile_name = "bear.log"
bear_num     = 0
def __init__(self,name):
self.name = name
print(" made a bear called %s" % (name))
self.logf  = open(Bear.logfile_name,"a")
Bear.bear_num += 1
self.created = datetime.datetime.now()
self.my_num = Bear.bear_num
self.logf.write("[%s] created bear #%i named %s\n" % \
(datetime.datetime.now(),Bear.bear_num,self.name))
self.logf.flush()

def growl(self,nbeep=5):
print("\a"*nbeep)

def __del__(self):
print("Bang! %s is no longer." % self.name)
self.logf.write("[%s] deleted bear #%i named %s\n" % \
(datetime.datetime.now(),self.my_num,self.name))
self.logf.flush()
# decrement the number of bears in the population
Bear.bear_num -= 1
# dont really need to close because Python will do the garbage collection
#  for us. but it cannot hurt to be graceful here.
self.logf.close()

def __str__(self):
age = datetime.datetime.now() - self.created
return " name = %s bear (age %s) number = %i (population %i)" % \
(self.name, age, self.my_num,Bear.bear_num)

In [ ]:
# add some dynamic aging to the bears
from bear2 import Bear as Bear2

In [ ]:
a = Bear2("Yogi")

In [ ]:
print(a)

In [ ]:
print(a)


### Emulating Numeric operations¶

you can define a whole bunch of ways that instances behave upon numerical operation

(e.g., __add__ is what gets called when you type instance_1 + instance_2)

__add__(self, other)

__sub__(self, other)

__mul__(self, other)

__div__(self, other)

__mod__(self, other)

__divmod__(self, other)

__pow__(self, other[, modulo])

__lshift__(self, other)

__rshift__(self, other)

__and__(self, other)

__xor__(self, other)


In [ ]:
%%file bear1.py

class Bear:
"""
class to show off addition (and multiplication)
"""
bear_num = 0
def __init__(self,name):
self.name = name
print(" made a bear called %s" % (name))
Bear.bear_num += 1
self.my_num = Bear.bear_num

## spawn a little tike
cub = Bear("progeny_of_%s_and_%s" % (self.name,other.name))
cub.parents = (self,other)
return cub

def __mul__(self,other):
## multiply (as in "go forth and multiply") is really the same as adding

In [ ]:
from bear1 import Bear as Bear1

In [ ]:
y = Bear1("Yogi") ; c = Bear1("Fuzzy")

In [ ]:
our_kid = y + c

In [ ]:
our_kid.

In [ ]:
our_kid.parents

In [ ]:
our_kid.parents[0].name

In [ ]:
our_kid.parents[1].name

In [ ]:
y.broken_limb = True

In [ ]:
our_kid1 = y * c


### Other Useful Specials¶

__dict__ : Dictionary containing the class's namespace.
__doc__ : Class documentation string, or None if undefined.
__name__: Class name.
__module__: Module name in which the class is defined. This attribute is "__main__" in interactive mode.
__bases__ : A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list.

In [ ]:
print(Bear1.__doc__)
print(Bear1.__name__)
print(Bear1.__module__)
print(Bear1.__bases__)
print(Bear1.__dict__)


## "Hiding" class data attributes¶

In [ ]:
class JustCounter:
__secretCount = 0
x = 0
def count(self):
self.__secretCount += 1
self.x += 1
print(self.__secretCount)

In [ ]:
counter = JustCounter()

In [ ]:
counter.count() ; counter.count()

In [ ]:
print(counter.x)

In [ ]:
print(counter.__secretCount)


no attribute is ever precisely private

double underscore attributes are exposed as object._className__attrName

In [ ]:
counter._JustCounter__secretCount


### A note on referencing...¶

In [ ]:
a = Bear("Yogi")

In [ ]:
a

In [ ]:
b = a

In [ ]:
b

In [ ]:
a.name = "Fuzzy"

In [ ]:
b.name

In [ ]:
Bear.bear_num

In [ ]:
import copy

In [ ]:
c = copy.copy(a)

In [ ]:
c # new memory location

In [ ]:
Bear.bear_num

In [ ]:
c.name

In [ ]:
c.name = "Smelly"

In [ ]:
a.name

In [ ]:
a.mylist = [1,2,3]

In [ ]:
b.mylist

In [ ]:
c.mylist

In [ ]:
d = copy.copy(a)
d.mylist

In [ ]:
d.name

In [ ]:
d.name = "Yogi"

In [ ]:
a.name

In [ ]:
a.mylist[0] = -1

In [ ]:
d.mylist

In [ ]:
e = copy.deepcopy(a)

In [ ]:
a.__dict__

In [ ]:
del a.logf

In [ ]:
e = copy.deepcopy(a)

In [ ]:
a.mylist[0] = "a"

In [ ]:
e.mylist

In [ ]:
Bear.bear_num


deepcopy: copies all attributes pointed to internally

# Subclassing & Inheritance¶

class classname(baseclass):

For example, class Flower(Plant):

Here we say that "the class Flower is a subclass of the base class Plant." Plant may itself be a subclass of LivingThing

attributes of the baseclass are inherited by the subclass

In [ ]:
class Plant:
num_known = 0
def __init__(self,common_name,latin_name=None):
self.latin_name = latin_name
self.common_name = common_name
Plant.num_known += 1

In [ ]:
class Flower(Plant):
has_pedals = True

In [ ]:
p = Plant("poison ivy")
e = Flower("poppy")

In [ ]:
e.has_pedals

In [ ]:
Plant.num_known

In [ ]:
Flower.__bases__[0].__name__


instantiation of a Flower reuses the __init__ from the Plant class. It also sets has pedals = True

In [ ]:
class Plant:
num_known = 0
def __init__(self,common_name,latin_name=None):
self.latin_name = latin_name
self.common_name = common_name
Plant.num_known += 1
def __str__(self):
return "I am a plant (%s)!" % self.common_name

class Flower(Plant):
has_pedals = True

def __str__(self):
return "I am a flower (%s)!" % self.common_name


now the __str__ method of Flower takes precedence over the __str__ method of the parent class

In [ ]:
f = Flower("rose") ; print(f)

In [ ]:
p = Plant("oak"); print(p)

In [ ]:
class Flower(Plant):
has_pedals = True

def __init__(self,common_name,npedals=5,pedal_color="red",latin_name=None):
## call the __init__ of the parent class
Plant.__init__(self,common_name,latin_name=latin_name)
self.npedals = npedals
self.pedal_color = pedal_color

def __str__(self):
return "I am a flower (%s)!" % self.common_name


we can still use the parent class' __init__

In [ ]:
f = Flower("rose") ; print(f)

In [ ]:
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
A.__init__(self)
print("B")

In [ ]:
a = A()

In [ ]:
b = B()


## Multiple Inheritances¶

class Flower1(Plant,EdibleFood,SmellyStuff)

when executing a method the namespace of:

• Flower1 is searched first
• Plant second (and it's baseclasses...and their baseclasses)
• EdibleFood second (and it's baseclasses...and their baseclasses)
• SmellyStuff second (and it's baseclasses...and their baseclasses)

## Errors (& Handling)¶

#### There are many different kinds of exceptions that can be raised by an error and each of them can be handled differently...¶

In [ ]:
3.1415/0

In [ ]:
def this_fails():
x = 3.1415/0
try:
this_fails()
except ZeroDivisionError as detail:
print('Handling run-time error:',detail )


import exceptions http://docs.python.org/library/exceptions.html

exception BaseException
exception Exception
exception StandardError
exception ArithmeticError
exception LookupError
exception EnvironmentError
exception AssertionError
exception AttributeError
exception EOFError
exception GeneratorExit
exception IOError
exception ImportError
exception IndexError
exception KeyError
exception KeyboardInterrupt
exception MemoryError
exception NameError
exception OverflowError
exception ReferenceError
exception RuntimeError
exception StopIteration
exception SyntaxError
exception SystemError
exception SystemExit
exception TypeError
exception ValueError
exception VMSError
exception WindowsError
exception ZeroDivisionError
exception Warning
exception UserWarning
exception DeprecationWarning
exception PendingDeprecationWarning
exception SyntaxWarning
exception RuntimeWarning
exception FutureWarning
exception ImportWarning

In [ ]:
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print("division by zero!")
else:
print("result is", result)
finally:
print("executing finally clause")

In [ ]:
divide(2,1)

In [ ]:
divide(2,0)

In [ ]:
divide("2","1")


## Catch Multiple Error Types¶

In [ ]:
%%file catcherr.py
import sys
try:
f = open('myfile.txt')
i = int(s.strip())
except IOError as err:
print("I/O error: %s" % err)
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise


exc_info(...)

exc_info() -> (type, value, traceback)

Return information about the most recent exception caught by an except
clause in the current stack frame or in an older stack frame.
In [ ]:
run catcherr


## Raising Errors¶

we can raise errors in our codes (which themselves might be caught upstream)

In [ ]:
a = "cat food"
if a != "spam":
raise NameError("anything that isn't spam breaks my code")


## Errors are a family of classes (and subclasses)!¶

BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StandardError
|    +-- BufferError
|    +-- ArithmeticError
|    |    +-- FloatingPointError
|    |    +-- OverflowError
|    |    +-- ZeroDivisionError
|    +-- AssertionError
|    +-- AttributeError
|    +-- EnvironmentError
|    |    +-- IOError
|    |    +-- OSError
|    |         +-- WindowsError (Windows)
|    |         +-- VMSError (VMS)
|    +-- EOFError
|    +-- ImportError
|    +-- LookupError
|    |    +-- IndexError
|    |    +-- KeyError
|    +-- MemoryError
|    +-- NameError
|    |    +-- UnboundLocalError
|    +-- ReferenceError
|    +-- RuntimeError
|    |    +-- NotImplementedError
|    +-- SyntaxError
|    |    +-- IndentationError
|    |         +-- TabError
|    +-- SystemError
|    +-- TypeError
|    +-- ValueError
|         +-- UnicodeError
|              +-- UnicodeDecodeError
|              +-- UnicodeEncodeError
|              +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning


## We can create our own exception classes by subclassing others (e.g., Exception)¶

In [ ]:
import datetime

In [ ]:
class MyError(StopIteration):

def __init__(self,value=None):
## call the baseclass Exception __init__
Exception.__init__(self)
self.value = value
print("exception with %s at time %s" % (self.value,datetime.datetime.now()))

def __str__(self):
return "you said %s" % self.value

In [ ]:
raise MyError("darnit")

In [ ]:
%debug

In [ ]:
%pdb

In [ ]: