Unless explicitely stated in the code cell, content in this notebook was created and tested in Python 2.7. Cells that were tested under Python 3.x versus Python 2.7 are so noted.
The following topics can come in handy when attempting to understand multi-inheritance and complexities that can arise such as "the diamond pattern" and/or unexpected inheritance collisions:
In [1]:
# http://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance
# started with content above and then edited and added to it as needed for this demo
# also made the code Python 3.x compatible
class First(object):
objCount = 0
def __init__(self):
print("first")
self.objCount +=1
First.objCount +=1
def greetMe(self, name="Anonymous"):
print("Hello %s" %name)
class Second(First):
def __init__(self):
print("second")
self.objCount +=1 # note: if we used super() we would not need this line
# this line should effect Second and Fourth instances based on
# the inheritance implementation
Second.objCount +=1
def doSomething(self):
print("something stupid")
class Third(First):
myGVal = 1000
def __init__(self):
print("third")
self.myIVal = 13
Third.objCount +=1
def changeIVal(self):
self.myIVal *=10
class Fourth(Second, Third):
def __init__(self):
super(Fourth, self).__init__() # using super() is a best practice for "forward copmatibility"
print("that's it - Welcome to Fourth!")
Fourth.objCount +=1
class Fifth(Second, Third):
# this also works ...
def __init__(self):
Second.__init__(self) # this is not a best practice but is often easier
print("that's it - Welcome to Fifth!")
Fifth.objCount +=1
In [2]:
Fourth.__mro__ # Method Resolution Order
Out[2]:
In [3]:
phorth = Fourth()
In [4]:
phifth = Fifth()
In [5]:
phorth.doSomething()
In [6]:
phorth.myGVal
Out[6]:
In [7]:
try:
phorth.myIVal
except Exception as ee:
print(type(ee), ee)
In [8]:
turd = Third()
In [9]:
turd.myIVal
Out[9]:
In [10]:
turd.changeIVal()
turd.myIVal
Out[10]:
In [11]:
try:
phorth.myIVal
except Exception as ee:
print(type(ee), ee)
In [12]:
# not this side effect ... we inherit a method that now changes a global value
# but we do not inherit the global value since it was not called inti __init__
try:
phorth.changeIVal()
except Exception as ee:
print(type(ee), ee)
In [13]:
turd.myGVal
Out[13]:
In [14]:
sekond = Second()
In [15]:
sekond.greetMe("Mitch")
In [16]:
phurst = First()
In [17]:
anotherSekond = Second()
anotherThird = Third()
yetAnotherThird = Third()
anotherFourth = Fourth()
In [18]:
# look at the counts ... Not really what we're after yet ...
print("Object Counts:")
print("-"*72)
print("phurst", phurst.objCount)
print("sekond", sekond.objCount)
print("anotherSekond", anotherSekond.objCount)
print("turd", turd.objCount)
print("phorth", phorth.objCount)
print("phifth", phifth.objCount)
print("-------------------------")
print("First", First.objCount)
print("Second", Second.objCount)
print("Third", Third.objCount)
print("Fourth", Fourth.objCount)
print("Fifth", Fifth.objCount)
In [19]:
# Method Resolution Order
print(First.__mro__)
print(Second.__mro__)
print(Third.__mro__)
print(Fourth.__mro__)
In [20]:
# counting objects ...
# what we're really after is the ability to track each child and get a total for all objects in the heirarchy
# let's see if we can clean this up a bit ...
class First(object):
objCount = 0
First.objCount = 0
def __init__(self):
print("first")
self.objCount +=1
First.objCount +=1
def greetMe(self, name="Anonymous"):
print("Hello %s" %name)
class Second(First):
objCount = 0
Second.objCount = 0
def __init__(self):
print("second")
self.objCount +=1 # note: if we used super() we would not need this line
# this line should effect Second and Fourth instances based on
# the inheritance implementation
Second.objCount +=1
def doSomething(self):
print("something stupid")
class Third(First):
myGVal = 1000
objCount = 0
Third.objCount = 0
def __init__(self):
print("third")
self.myIVal = 13
self.objCount +=1
Third.objCount +=1
def changeIVal(self):
self.myIVal *=10
class Fourth(Second, Third):
objCount = 0
Fourth.ojbCount = 0
def __init__(self):
super(Fourth, self).__init__()
print("that's it - Welcome to Fourth!")
Fourth.objCount +=1
Second.objCount -=1 # correct for accidental increment caused by inheritence
class Fifth(Second, Third):
# this also works ...
objCount = 0
Fifth.objCount = 0
def __init__(self):
Second.__init__(self)
print("that's it - Welcome to Fifth!")
Fifth.objCount +=1
Second.objCount -=1 # correct for accidental increment caused by inheritence
In [21]:
# make some objects
phirst = First()
sekond = Second()
sekond2 = Second()
turd = Third()
thoid2 = Third()
thoid3 = Third()
thoid4 = Third()
phorth = Fourth()
phorth2 = Fourth()
phifth = Fifth()
Phifth2 = Fifth()
Phifth3 = Fifth()
In [22]:
# Instance object counts
print("phirst: %s" %phirst.objCount)
print("sekond: %s" %sekond.objCount)
print("sekond2: %s" %sekond2.objCount)
print("turd: %s" %turd.objCount)
print("thoid2: %s" %thoid2.objCount)
print("thoid3: %s" %thoid3.objCount)
print("thoid4: %s" %thoid4.objCount)
print("phorth: %s" %phorth.objCount)
print("phorth2: %s" %phorth2.objCount)
print("phifth: %s" %phifth.objCount)
print("Phifth2: %s" %Phifth2.objCount)
print("Phifth3: %s" %Phifth3.objCount)
In [23]:
# Counts for the whole class:
print("First(): %s" %First.objCount)
print("Second(): %s" %Second.objCount)
print("Third(): %s" %Third.objCount)
print("Fourth(): %s" %Fourth.objCount)
print("Fifth(): %s" %Fifth.objCount)
In [24]:
foist2 = First()
print("phirst: %s" %phirst.objCount)
print("foist2: %s" %foist2.objCount)
print("First(): %s" %First.objCount)
In [1]:
# Object for Coordinates of x and y
# uses over-rides to python language methods and operators
# note: Pythons does not typicall use getter and setter methods
# Instead, note how we access x and y in next cells ...
class Coordinate(object):
def __init__(self, x1, y1):
self.x = x1
self.y = y1
def __str__(self):
return "<%s,%s>" %(self.x, self.y) # this format approach prserves original full value
# regardless if it is float or int
def __eq__(self, otherCoordinate):
if self.x == otherCoordinate.x and self.y == otherCoordinate.y:
return True
else:
return False
def __add__(self, otherCoordinate):
return Coordinate(self.x + otherCoordinate.x, self.y + otherCoordinate.y)
In [2]:
coor1 = Coordinate(2, 3)
print("coor1: %s" %coor1)
coor2 = Coordinate(-5, 15)
print("coor2: %s" %coor2)
coor3 = coor1.__add__(coor2)
print("coor3: %s" %coor3)
print(type(coor3)) # Coordinate
print(isinstance(coor3, Coordinate)) # True
print(coor3.x) # -3
print(coor3.y) # 18
print(coor3) # <-3,18>
print("-"*72)
coor4 = coor1 + coor2
print("coor4: %s" %coor4) # <-3,18>
print(type(coor4)) # Coordinate
print(isinstance(coor4, Coordinate)) # True
print(coor4.x) # -3
print(coor4.y) # 18
print("coor3 == coor4: %s" %(coor3 == coor4))
print("coor2 == coor4: %s" %(coor2 == coor4))
These code examples work as indicated for Python 2.7 or Python 3.x. Note that these differences imply that in Python 2.7 you must instantiate at least one instance of the class to call the class method with. In Python 3.6, tests were successful without needing to do this.
In [1]:
# the object for this example was an answer to a problem first encountered on www.hackerrank.com
# this cell works in Python 2.7 and 3.6
# it sets up a class with an example method and nothing else in it
class Calculator:
def power(self, n, p):
if n < 0 or p < 0:
raise ValueError("n and p should be non-negative")
else:
return n**p
In [3]:
# to use this method like a static method call, Python 2.7 still requires
# instantiation of the class method. Run this cell to see Python 3.6 code errors
# and the working version in Python 2.7
# works in Python 2.7 ... includes 3.6 only code to show the error:
try:
print(Calculator.power(Calculator, 3, 7)) # 3.6 compatible code
except Exception as ee:
print("Error: %s" %ee)
print("Using Python 2.7 alternate code:")
workingCalculator = Calculator()
print(Calculator.power(workingCalculator, 3, 7))
In [2]:
# run this same content in Python 3.6 and no error is thrown, the first line just works
# without needing to instantiate the class
class Calculator:
# repeated from earlier cell so this cell can be re-tested without re-running earlier cells
def power(self, n, p):
if n < 0 or p < 0:
raise ValueError("n and p should be non-negative")
else:
return n**p
try:
print(Calculator.power(Calculator, 3, 7)) # 3.6 compatible code
except Exception as ee:
print("Error: %s" %ee)
print("Using Python 2.7 alternate code:")
workingCalculator = Calculator()
print(Calculator.power(workingCalculator, 3, 7))
In [4]:
# Under Python 2.7, both type() tests produce identical output that looks like this:
# <type 'instancemethod'>
class Foo(object):
# code modified slightly from this post:
# http://stackoverflow.com/questions/37370578/different-way-to-create-an-instance-method-object-in-python
def method(self):
print("Foo Method Works!")
f = Foo()
print(type(f.method))
print(type(Foo.method))
In [1]:
# Under Python 3.6, the two type tests now produce diferent output that looks like this:
# <class 'method'>
# <class 'function'>
class Foo(object):
# code modified slightly from this post:
# http://stackoverflow.com/questions/37370578/different-way-to-create-an-instance-method-object-in-python
def method(self):
print("Foo Method Works!")
f = Foo()
print(type(f.method))
print(type(Foo.method))
In [2]:
# Tested in Python 3.6 and Python 2.7 (worked with both)
# Intended to just practice and demo some concepts ... not what we do in real world to get this output
outLst = [0, 0, 0]
outLst[0] = instance_method = f.method
outLst[1] = instance_method.__func__ is Foo.method
outLst[2] = instance_method.__self__ is f
outLst2 = [(str(elem) + "\n") for elem in outLst] # converts all to string and adds \n to end
for elem in outLst2:
print(elem[:-1]) # index refereence strips off \n on end since print() will add newline anyway
In [3]:
# Tested in Python 3.6
# both lines produce: "Foo Method Works!"
try:
# try-except added because of tests shown in next cell ...
Foo.method(Foo) # requires us to pass in self as the class in order to work in Python 3.6
except Exception as ee:
print(type(ee))
print(ee)
f.method() # just works
In [5]:
# Tested in Python 2.7
''' output from python 2.7 test:
<type 'exceptions.TypeError'>
unbound method method() must be called with Foo instance as first argument (got type instance instead)
Foo Method Works!
'''
try:
# ran into trouble in Python 2.7 ... so this code captures the error when run in that version
Foo.method(Foo) # requires us to pass in self as the class in order to work in PY3.6, but 2.7 still has problems
except Exception as ee:
print(type(ee))
print(ee)
f.method() # just works
In [6]:
# Tested in Python 2.7 and 3.6
# making the static call work in Python 2.7 seems to require creating an instance first
Foo.method(f)
In [6]:
# tested in Python 3.6 and Python 2.7 (tests in next cells)
class GetDeclaredName(object):
'''Get Name of Variable or Function Passed Into Object. Use inside methods to identify what got passed in via args.'''
def name_of_declaredEement(self, value):
''' name_of_declaredElement -->\n\nreturns name of declared element (variable or function) passed into it.'''
# code first appeared on StackOverflow in this posting: http://stackoverflow.com/a/1538399/7525365
for n,v in globals().items():
if v == value:
return n
return None
In [7]:
#Tested in Python 3.6 and Python 2.7
gdn = GetDeclaredName()
lst = [1,2,3]
try:
print(GetDeclaredName.name_of_declaredEement(GetDeclaredName, lst)) # will throw error in Python 2.7
except Exception as ee: # works in Python 3.6
print(type(ee))
print(ee)
In [8]:
# more tests in both versions of Python
try:
# function does not like objects or their attributes (variables) - fails in Python 2.7 and 3.6
print(GetDeclaredName.name_of_declaredEement(GetDeclaredName, coor3))
except Exception as ee:
print(type(ee))
print(ee)
try:
# function does not like objects or their attributes (variables) - fails in Python 2.7 and 3.6
print(GetDeclaredName.name_of_declaredEement(gdn, coor3.x))
except Exception as ee:
print(type(ee))
print(ee)
try:
print(GetDeclaredName.name_of_declaredEement(gdn, lst)) # this works in Python 2.7 and throws error in Python 3.6
except Exception as ee:
print(type(ee))
print(ee)
print("coor3.x exists and has this value: %d" %coor3.x)
In [95]:
from abc import ABCMeta, abstractmethod
class Book:
__metaclass__ = ABCMeta # sets up abstract class
def __init__(self,title,author): # abstract classes can be subclassed
self.title=title # they cannot be instantiated
self.author=author
print("Creating Book: " + self.title + " by " + self.author)
@abstractmethod # abstract method definition (beginning)
def display(): pass # abstract method definition (continued)
class MyBook(Book): # if this class adds any abstract methods
def __init__(self, title, author, price): # it would be abstract too
# Book.__init__(self, title, author) # to be a concrete class that can be instantiated
super(MyBook, self).__init__(title,author) # it must implement all inherited abstract methods
self.price = price # note: Book. and super() solutions both work
print("Creating MyBook: %s" %self.price) # but super() is a best practice for forward compatibility
def display(self):
print("Title: %s" %self.title)
print("Author: %s" %self.author)
print("Price: %d" %self.price)
In [96]:
sciFiAmazing = MyBook("The Lord of the Rings", "J.R.R. Tolkien", 8.5)
In [97]:
sciFiAmazing.price
Out[97]:
In [98]:
sciFiAmazing.display()
sciFiAmazing.title + " by " + sciFiAmazing.author
Out[98]:
In [ ]: