Python Objects

CS314

Review

Previously

  • Python Basics.

This lecture

  • Python Objects

Python Objects

In [63]:
class Point:
  x = 0
  y = 0
In [64]:
p = Point()
In [65]:
p.x
Out[65]:
0
In [66]:
p.y
Out[66]:
0
In [67]:
p.x = 7
In [68]:
q = Point()
In [69]:
q.x
Out[69]:
0

What about this?

In [70]:
q.x = 10
In [71]:
p.x
Out[71]:
7

Class-based model

  • Have classes that describe the format of objects.
  • Create objects by stating the class of the object to be created.
  • The created object is called an instance of the class

Class-based model

  • In a class based model, the class is sometimes an object too (as is the case in Python)
  • Q: what is the class of the class object?
    • The “meta-class”? But then do we have a meta-meta-class?
    • many possibilities, but no clear answer
    • turns out to be a nasty problem!

What’s the alternative?

  • Suppose we didn’t have classes
  • How would one survive?

Prototype-based models

  • Just have objects
    • Create a new object by cloning another one – Add/update fields later
  • Benefits:
    • Simplifies the definition of the language – Avoids meta-class problem
  • Drawbacks:
    • Don’t have classes for static typing
  • Python has hints of a prototype-based language.

Flavor of prototype-based model

In [72]:
class Point:
  x = 0
  y = 0
In [73]:
Point
Out[73]:
__main__.Point
In [74]:
Point.__dict__.keys()
Out[74]:
dict_keys(['__module__', 'x', 'y', '__dict__', '__weakref__', '__doc__'])

Notice how classes are just a namespace!

In [75]:
Point.x
Out[75]:
0
In [76]:
Point.y
Out[76]:
0
In [77]:
p = Point()
In [78]:
p.x
Out[78]:
0
In [79]:
p.__dict__
Out[79]:
{}
In [80]:
p.x = 10
In [81]:
p.__dict__
Out[81]:
{'x': 10}
In [82]:
q = Point()
In [83]:
q.x
Out[83]:
0
In [84]:
Point.x = 30
In [85]:
q.x
Out[85]:
30
In [89]:
p.x
In [90]:
q.__dict__
Out[90]:
{}
In [93]:
p.__dict__
Out[93]:
{'x': 20}

Class Methods

In [94]:
class Point:
  x = 0
  y = 0
  def move(self,dx,dy):
    self.x = self.x + dx
    self.y = self.y + dy
In [95]:
Point.x
Out[95]:
0
In [96]:
Point.y
Out[96]:
0
In [97]:
p = Point()
In [98]:
p.move
Out[98]:
<bound method Point.move of <__main__.Point object at 0x7fe450667908>>
In [99]:
Point.move
Out[99]:
<function __main__.Point.move(self, dx, dy)>
In [100]:
p.move(10,100)
In [101]:
Point.move(p,10,100)
In [102]:
f = p.move
(p.x,p.y)
Out[102]:
(20, 200)
In [103]:
for i in range(10): f(100,100)
(p.x,p.y)
Out[103]:
(1020, 1200)

Constructor

In [104]:
class Point:
  x = 0
  y = 0
  def move(self,dx,dy):
    self.x = self.x + dx
    self.y = self.y + dy
  def jump(self,x,y):
    self.x = x
    self.y = y
  def __init__(self,x,y):
    self.jump(x,y)
  def __repr__(self): 
    return "Point(" + str(self.x) + "," + str(self.y) + ") with id " + hex(id(self))
  def __str__(self):
    return "x = " + str(self.x) + " y = " + str(self.y)
In [105]:
p = Point(100,100)
In [106]:
p
Out[106]:
Point(100,100) with id 0x7fe4506675f8
In [107]:
print(p)
x = 100 y = 100
In [108]:
str(p)
Out[108]:
'x = 100 y = 100'
  • repr goal is to be unambiguous
  • str goal is to be readable, e.g. str(5) == str("5") but repr(5) != repr("5")

Subclassing vs. Subtyping

  • Subclass (Inheritance) — an implementation notion

    • Factor out repeated code
    • To create a new class, write only the differences
  • Subtype (Substitution) — a specification notion

    • B is a subtype of A iff an object of B can masquerade as an object of A in any context
    • About satisfiability (behavior of a B is a subset of A’s spec)

Subclassing

  • Super-class method can be overwritten in sub-class
In [109]:
class Point:
  x = 0
  y = 0
  def __init__(self,x,y):
    self.x = x
    self.y = y
  def move(self,dx,dy):
    s = "Moving from " + str(self.x) + " " + str(self.y) + " to "
    self.x = self.x + dx
    self.y = self.y + dy
    print(s + str(self.x) + " " + str(self.y))
  def draw(self):
    print("Plot a point at " + str(self.x) + " " + str(self.y))
In [ ]:
class ColoredPoint(Point):
  color = "blue"
  def __init__(self,x,y,color):
     Point.__init__(self,x,y)
     self.color = color
  def draw(self):
    print("Set color to: " + str(self.color))
    Point.draw(self)

Subtyping

  • Structural Subtyping (by-elements-of-object):
    • Point and Clown are all "structural subtypes" of an object with a "move" method
In [111]:
class Point:
  x = 0
  y = 0
  def move(self,dx,dy):
    self.x = self.x + dx
    self.y = self.y + dy
    
class Circle:
  x = 0
  y = 0
  radius = 10
  def move(self,dx,dy):
    self.x = self.x + dx
    self.y = self.y + dy

class Clown:
  def move(self,dx,dy):
    print("Ha! Ha! Fooled you!") 
  def say_joke():
    print("knock knock!");

Subtyping

  • Note the "Duck-Typing" Polymorphism.
    • do_some_moving works for any object with a "move" method
    • Structural (by-elements-of-object) Subtyping: Point and Clown are all "structural subtypes" of an object with a "move" method
  • Vs. Subclassing: Programmer says C1 inherits from C2 or C1 implements C2.
In [110]:
def do_some_moving(p):
  for x in [-2,-1,1,2]:
     for y in [-2,-1,1,2]:
        p.move(x,y)
In [112]:
p = Point()
q = Clown()
do_some_moving(p)
do_some_moving(q)
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!
Ha! Ha! Fooled you!

Structural, nominal subtyping

  • p and q of the same type?
    • In Java: No
    • In Python: Yes: structural subtyping (using fields/methods to determine subtyping)

Inheritance (subclassing)

Overriding

  • Super-class method can be overwritten in sub-class
  • Polymorphism
    • external clients can write code that handles many different kinds of objects in the same way
    • don’t care about implementation details: as long as the object knows to draw itself, that’s good enough
    • Super-class can have methods that are not overridden, but that work differently for different sub-classes.
    • For example: super-class method functionality changes because the super-class calls a method that gets overwritten in the sub-class

Stepping away from Python

  • What are the fundamental issues with inheritance?
    • Dispatch mechanism
    • Overloading vs. overriding
    • How to decide on the inheritance graph?

Dispatch mechanism

  • If an object calls draw and is an instance of Shape, it is unclear at compile time which draw to call between the Rectangle class and the Circle class.
void drawAll(Shape[] shapes) { 
    for(Shape s: shapes)
        s.draw()
 }
  • most compilers use v-tables

Overloading vs. overriding

  • what’s the difference?
In [133]:
# Overlaoding a function to take multiple arguments
def add(*args):
  
    # if datatype is int, initialize answer as 0. Use the isinstance function!
    if isinstance(args[0], int):
        answer = 0
          
    # if datatype is str, initialize answer as ''. Use the isinstance function!
    if isinstance(args[0], str):
        answer =''
  
    # Traverse through the arguments
    for x in args:
        # This will do addition if the arguments are int. Or concatenation if the arguments are str
        answer = answer + x
  
    print(answer)
  
# Integer
add(5, 6)
  
# String
add('Hi ', 'Geeks')
18
10
Hi Geeks

How to decide on the inheritance graph?

  • not always obvious
  • Which should be a sub-class of which?
  • Answer is not clear...

Option 1: Rectangle isa Square

Option 2: Square isa Rectangle

Option 3

  • Store only what is needed (one field for square)
  • Have to write two area methods

Summary of (single) inheritance

  • Inheritance is a powerful mechanism
  • From the programmer’s perspective, difficulty is in defining the inheritance diagram
  • From a language implementer’s perspective, difficulty is in making dynamic dispatch work

Multiple inheritance

In [ ]:
class ColorTextBox(ColorBox,TextPoint): 
    def draw(self,screen,pos):
        ColorBox.draw(self,screen,pos) 
        r=TextPoint.draw(self,screen,pos) 
        return r
    def __str__(self):
        return ColorBox.__str__(self) + " text: " + str(self.text)
  • What are the issues?
    • Inheritance tree becomes a DAG

What are the issues?

  • Issue 1: fields/methods with the same name inherited from two different places
  • Issue 2: diamond problem, same exact field inherited by two different paths
In [114]:
class X:
    a = 0
    def f(self): 
        return "X"
class Y:
    b = 1
    def f(self): 
        return "Y"
class Z:
    c = 2
    def f(self): 
        return "Z"

class A(X,Y): pass
class B(Y,Z): pass

class M(A,B,Z): pass

What are the issues?

  • Because of these issues, Java does not allow multiple inheritance
  • Java does allow multiple inheritance of interfaces. How is that different from general multiple inheritance?

How Python solves these issues

  • When you say: class C(C1, C2, ...)
    • For any attribute not defined in C, Python first looks up in C1, and parents of C1
    • If it doesn’t find it there, it looks in C2 and parents of C2
    • And so on...
    • What kind of search is this?

Method resolution order

  • DFS search for method resolution.
  • If a super class is before a sub class on the method reoluation path, delay the super class.
    • Only visit a super class after its subclasses are visited.
  • M -> A -> X -> [Object] -> [Y] -> [Object] -> B -> Y -> [Object] -> Z -> Object
  • The search of classes in [ ] is delayed until their subclasses are searched.
In [115]:
M.__mro__
Out[115]:
(__main__.M,
 __main__.A,
 __main__.X,
 __main__.B,
 __main__.Y,
 __main__.Z,
 object)
In [116]:
M().f()
Out[116]:
'X'

Does this solve the two issues?

  • Issue 1: fields/methods with the same name inherited from two different places
    • Solved because we give leftmost parent priority
  • Issue 2: diamond problem, same exact field inherited by two different paths
    • Solved because there is only one copy

A few more things about scoping model

what's happening here?

In [1]:
def g(y): return y + n
g(5)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-4cfd5571a5dc> in <module>
      1 def g(y): return y + n
----> 2 g(5)

<ipython-input-1-4cfd5571a5dc> in g(y)
----> 1 def g(y): return y + n
      2 g(5)

NameError: name 'n' is not defined
  • Var not bound, but it's done at runtime...
  • what about this? Will this work?
In [2]:
n = 10
g(5)
Out[2]:
15
In [1]:
n = 100
g(5)
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-1-554e81f72bcc> in <module>
      7 
      8 x = 10
----> 9 f()

<ipython-input-1-554e81f72bcc> in f()
      1 def f():
----> 2   print("A", x)
      3   y = x
      4   print("B")
      5   x = x + 1

UnboundLocalError: local variable 'x' referenced before assignment

Here is another issue in Python:

In [120]:
n=4
def f():
  n = "smash"
  print(n)

f()
n
smash
Out[120]:
4
In [121]:
n=4
def f():
  global n
  n = "smash"
  print(n)

f()
n
smash
Out[121]:
'smash'

Here is another workaround:

In [122]:
n = [100]

def f():
  n[0] = "smash"
  print(n)

f()
n
['smash']
Out[122]:
['smash']
  • Python doesn't allow you to assign into globals
  • It allows you to mutate objects pointed to by globals
    • note difference between changing "links" and changing the "contents" of a box
  • ok, let's try this...
In [123]:
x = 10

def f():
  x = x + 1
  print(x)

f()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-123-7ce2ac12f354> in <module>
      5   print(x)
      6 
----> 7 f()

<ipython-input-123-7ce2ac12f354> in f()
      2 
      3 def f():
----> 4   x = x + 1
      5   print(x)
      6 

UnboundLocalError: local variable 'x' referenced before assignment
  • or, let's try this
In [136]:
x = 10

def f():
  print("A", x)
  y = x
  print("B")
  x = x + 1
  print(x)


f()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-136-554e81f72bcc> in <module>
      7 
      8 x = 10
----> 9 f()

<ipython-input-136-554e81f72bcc> in f()
      1 def f():
----> 2   print("A", x)
      3   y = x
      4   print("B")
      5   x = x + 1

UnboundLocalError: local variable 'x' referenced before assignment
In [135]:
x = 10

def f():
  global x
  x = x + 1
  print(x)

f()
11
  • but of course, if we do the following it's the global