Why use classes?
¨ Implementing new objects
· Multiple instances
· Customization via inheritance
· Operator overloading
Class topics
¨ Class basics
¨ The class statement
¨ Class methods
¨ Attribute inheritance
¨ Operator overloading
¨ Name spaces and scopes
¨ OOP, inheritance, and composition
¨ Classes and methods are objects
¨ Odds and ends
¨ Class gotchas
How it works
·
All about:
“object.attr”
·
Kicks off a
search for first “attr” => “inheritance”
·
Searches trees of
linked namespace objects
·
Class objects:
supers and subs
·
Instance objects:
generated form a class
·
Classes can also
define expression behavior
class C1: … # make class objects (ovals)
class C2: …
class C3(C1, C2): … # links to superclasses, search order
I1 = C3() # make instance objects
(rectangles)
I2 = C3() # linked to their class
I1.x # finds customized version
in C3
The world’s simplest Python class
>>> class rec: pass # empty namespace object
>>> rec.name = 'Bob' # just objects with attributes
>>> rec.age = 40
>>>
>>> print rec.name # like a struct in C, a record
Bob
>>> x = rec() # instances inherit class names
>>> y = rec()
>>> x.name, y.name
('Bob', 'Bob')
>>> x.name = 'Sue' # but assignment changes x only
>>> rec.name, x.name, y.name
('Bob', 'Sue', 'Bob')
# really just linked dictionaries
>>> rec.__dict__.keys()
['age', '__module__', '__doc__', 'name']
>>> x.__dict__.keys()
['name']
>>> y.__dict__.keys()
[]
>>> x.__class__
<class __main__.rec at 0x00BAFF60>
# even methods can be created on the fly
>>> def upperName(self):
return self.name.upper() # still needs a self
>>> rec.method = upperName
>>> x.method()
'SUE'
>>> y.method() # run method to process y
'BOB'
>>> rec.method(x) # can call through instance or class
'SUE'
Why OOP?
·
Inheritance:
basis of specializing, customizing software -- lower in tree means
customization
·
Program by
customizing in new levels of hierarchy, not changing
·
OOP great at code
reuse, encapsulation, structure
·
Example: an
employee database app
1. Classes generate multiple instance objects
· Classes implement new objects: state + behavior
· Calling a class like a function makes a new instance
· Each instance inherits class attributes, and gets its own
· Assignments in class statements make class attributes
· Assignments to “self.attr” make per-instance attributes
>>> class FirstClass: # define a class object
... def setdata(self, value): # define class methods
... self.data = value # self is the instance
... def display(self):
... print self.data # self.data: per instance
>>> x = FirstClass() # make two instances
>>> y = FirstClass() # each is a new namespace
>>> x.setdata("King Arthur") # call methods: self=x/y
>>> y.setdata(3.14159)
>>> x.display() # self.data differs in each
King Arthur
>>> y.display()
3.14159
>>> x.data = "New value" # can get/set attributes
>>> x.display() # outside the class too
New value
3 objects, and 3 namespaces
2. Classes are specialized by inheritance
· Superclasses listed in parenthesis in class's header
· Classes inherit attributes from their superclasses
· Instances inherit attributes from all accessible classes
· Logic changes made in subclasses, not in-place
>>> class SecondClass(FirstClass): # inherits setdata
... def display(self): # changes display
... print 'Current value = "%s"' % self.data
>>> z = SecondClass()
>>> z.setdata(42) # setdata found in FirstClass
>>> z.display() # finds/calls overridden method
Current value = "42"
>>> x.display() # x is a FirstClass instance
New value
3. Classes can intercept Python operators
· Methods with names like "__X__" are special hooks
· Called automatically when Python evaluates operators
· Classes may override most built-in type operations
· Allows classes to integrate with Python's object model
>>> class ThirdClass(SecondClass): # isa SecondClass
... def __init__(self, value): # "ThirdClass(x)"
... self.data = value
... def __add__(self, other): # "self + other"
... return ThirdClass(self.data + other)
... def __mul__(self, other):
... self.data = self.data * other # "self * other"
>>> a = ThirdClass("abc") # new __init__ called
>>> a.display() # inherited method
Current value = "abc"
>>> b = a + 'xyz' # new __add__ called
>>> b.display()
Current value = "abcxyz"
>>> a * 3 # new __mul__ called
>>> a.display()
Current value = "abcabcabc"
A More Realistic Example:
using classes as database records
see CD’s Extras\people
è Dynamic typing and
polymorphism are keys to Python
è “self” and “__init__” are
key concepts in Python OOP
¨ Class
· An object (and statement) which defines inherited members and methods
¨ Instance
· Objects created from a class, which inherit its attributes; each instance is a new namespace
¨ Member
· An attribute of a class or instance object, that’s bound to an object
¨ Method
· An attribute of a class object, that’s bound to a function object (a callable member)
¨ Self
· By convention, the name given to the implied instance object in methods
¨ Inheritance
· When an instance or class accesses a class’s attributes
¨ Superclass
· Class or classes another class inherits attributes from
¨ Subclass
· Class which inherits attribute names from another class
¨ Python’s main OOP tool (like C++)
¨ Superclasses are listed in parenthesis
¨ Special protocols, operator overloading: __X__
¨ Multiple inheritance: “class X(A, B, C)” (df, l-to-r)
General form
class <name>(superclass,…): # assign to name
data = value # shared class data
def method(self,…): # methods
self.member = value # per-instance data
Example
· “class” introduces a new local scope
· Assignments in “class” create class object attributes
· “self.name = X” creates/changes instance attribute
class Subclass(Superclass): # define subclass
data = 'spam' # assign class attr
def __init__(self, value): # assign class attr
self.data = value # assign instance attr
def display(self):
print self.data, Subclass.data # instance, class
>>> x, y = Subclass(1), Subclass(2)
>>> x.display(); y.display()
1 spam
2 spam
¨ “class” statement creates and assigns a class object
¨ Calling a class object generates an instance object
¨ Class methods provide behavior for instance objects
¨ Methods are nested “def” functions, with a ‘self’
¨ ‘self’ is passed the implied instance object
¨ Methods are all ‘public’ and ‘virtual’ in C++ terms
calling
methods from instances
Example
class NextClass: # define class
def printer(self, text): # define method
print text
>>> x = NextClass() # make instance
>>> x.printer('Hello world!') # call its method
Hello world!
>>> NextClass.printer(x, 'Hello world!') # class method
Hello world!
Commonly used for calling
superclass constructors
class Super:
def __init__(self, x):
…default code…
class Sub(Super):
def __init__(self, x, y):
Super.__init__(self, x) # run superclass init
…custom code… # do my init actions
I = Sub(1, 2)
¨ Inheritance uses attribute definition tree (namespaces)
¨ “object.attr” searches up namespace tree for first “attr”
¨ Lower definitions in the tree override higher ones
Attribute tree construction:
1. Instance Þ
assignments to ‘self’ attributes
2. Class Þ
statements (assignments) in class statements
3. Superclasses Þ
classes listed in parenthesis in header
class S1: class S2:
class
X(S1, S2): def attr(self,…):
self.attr = V object = X()
object.attr ?
¨ Inheritance finds names in subclass before superclass
¨ Subclasses may inherit, replace, extend, or provide
¨ Direct superclass method calls: Class.method(self,…)
>>> class Super:
... def method(self):
... print 'in Super.method'
...
>>> class Sub(Super):
... def method(self):
... print 'starting Sub.method'
... Super.method(self)
... print 'ending Sub.method'
...
>>> x = Super()
>>> x.method()
in Super.method
>>> x = Sub()
>>> x.method()
starting Sub.method
in Super.method
ending Sub.method
superclass subclass instance
file: specialize.py
class Super:
def method(self):
print 'in Super.method' # default
def delegate(self):
self.action() # expected
class Inheritor(Super):
pass
class Replacer(Super):
def method(self):
print 'in Replacer.method'
class Extender(Super):
def method(self):
print 'starting Extender.method'
Super.method(self)
print 'ending Extender.method'
class Provider(Super):
def action(self):
print 'in Provider.action'
if __name__ == '__main__':
for klass in (Inheritor, Replacer, Extender):
print '\n' + klass.__name__ + '...'
klass().method()
print '\nProvider...'
Provider().delegate()
% python specialize.py
Inheritor...
in Super.method
Replacer...
in Replacer.method
Extender...
starting Extender.method
in Super.method
ending Extender.method
Provider...
in Provider.action
¨ Lets classes intercept normal Python operations
¨ Can overload all Python expression operators
¨ Can overload object operations: print, call, qualify,...
¨ Makes class instances more like built-in types
¨ Via providing specially-names class methods
class Number:
def __init__(self, start): # on Number()
self.data = start
def __add__(self, other): # on x + other
return Number(self.data + other)
>>> X = Number(4)
>>> Y = X + 2
>>> Y.data
6
Common operator overloading methods
¨ Special method names have 2 “_” before and after
¨ See Python manuals or reference books for the full set
Method |
Overloads |
Called for |
__init__ |
constructor |
object creation: X() |
__del__ |
destructor |
object reclamation |
__add__ |
operator ‘+’ |
X + Y |
__or__ |
operator ‘|’ |
X | Y |
__repr__ |
printing |
print X, `X` |
__call__ |
function calls |
X() |
__getattr__ |
qualification |
X.undefined |
__getitem__ |
indexing |
X[key], iteration, in |
__setitem__ |
qualification |
X[key] = value |
__len__ |
length |
len(X), truth tests |
__cmp__ |
comparison |
X == Y, X < Y |
__radd__ |
operator ‘+’ |
non-instance + X |
Examples
Ø
__call__: a function interface, with memory
>>> class Callback:
def __init__(self, color): # state information
self.color = color
def __call__(self, *args): # support calls
print 'turn', self.color
>>> cb1 = Callback('blue') # ‘remember’ blue
>>> cb2 = Callback('green')
>>>
>>> Button(command=cb1,…) # register handler
>>> cb1() # on events…
turn blue
>>> cb2()
turn green
>>> cb3 = (lambda color='red': 'turn ' + color) # or: defaults
>>> cb3()
'turn red'
Ø
__setattr__: attribute privacy for Python
See Extras\Misc\privates.py on class CD
Examples
¨
getitem intercepts
all index references
>>> class indexer:
... def __getitem__(self, index):
... return index ** 2
...
>>> X = indexer()
>>> for i in range(5):
... print X[i], # __getitem__
...
0 1
¨
getattr catches
undefined attribute references
>>> class empty:
... def __getattr__(self, attrname):
... return attrname + ' not supported!'
...
>>> X = empty()
>>> X.age # __getattr__
'age not supported!'
¨
init called on instance creation
¨
add intercepts ‘+’ expressions
¨
repr returns a string when called by ‘print’
>>> class adder:
... def __init__(self, value=0):
... self.data = value # init data
... def __add__(self, other):
... self.data = self.data + other # add other
... def __repr__(self):
... return `self.data` # to string
...
>>> X = adder(1) # __init__
>>> X + 2; X + 2 # __add__
>>> X # __repr__
5
¨ Unqualified names (“X”) deal with lexical scopes
¨ Qualified names (“O.X”) use object namespaces
¨ Scopes initialize object namespaces: modules, classes
The “Zen” of Python Namespaces
mod.py
# all 5 Xs are different variables
X = 1 # global
def f():
X = 2 # local
class C:
X = 3 # class
def
m(self):
X = 4 # local
self.X = 5 # instance
Unqualified names: global unless assigned
¨ Assignment: “X = value”
· Makes names local: creates or changes name in the current local scope, unless declared ‘global’
¨ Reference: “X”
· Looks for names in the current local scope, then the current global scope, then the outer built-in scope
Qualified names: object name-spaces
¨ Assignment: “X.name = value”
· Creates or alters the attribute name in the namespace of the object being qualified
¨ Reference: “X.name”
· Searches for the attribute name in the object, and then all accessible classes above it (none for modules)
Namespace dictionaries
¨
Object
name-spaces: built-in “__dict__” attributes
¨
Qualification ==
indexing a name-space dictionary
· To get a ‘name’ from a module “M”:
· M.name
· M.__dict__[’name’]
· sys.modules['M'].name
· sys.modules['M'].__dict__['name']
· sys.__dict__['modules']['M'].__dict__['name']
¨ Attribute inheritance == searching dictionaries
>>> class super:
... def hello(self):
... self.data = 'spam' # in self.__dict__
...
>>> class sub(super):
... def howdy(self): pass
...
>>> X = sub()
>>> X.__dict__ # a new name-space/dict
{}
>>> X.hola = 42 # add member to X object
>>> X.__dict__
{'hola': 42}
>>> sub.__dict__
{'__doc__': None, 'howdy': <function howdy at 762100>}
>>> super.__dict__
{'hello': <function hello at 769fd0>, '__doc__': None}
>>> X.hello()
>>> X.__dict__
{'data': 'spam', 'hola': 42}
¨ Inheritance based on attribute qualification
¨ In OOP terminology: ‘is-a’ relationship
¨ On “X.name”, looks for “name” in:
1. Instance ÜX’s own name-space
2. Class Üclass that X was made from
3. Superclasses Üdepth-first, left-to-right
Example: a zoo hierarchy in Python
file: zoo.py
class Animal:
def reply(self): self.speak()
def speak(self): print 'spam'
class Mammal(Animal):
def speak(self): print 'huh?'
class Cat(Mammal):
def speak(self): print 'meow'
class Dog(Mammal):
def speak(self): print 'bark'
class Primate(Mammal):
def speak(self): print 'Hello world!'
class Hacker(Primate): pass
% python
>>> from zoo import Cat, Hacker
>>> spot = Cat()
>>> spot.reply() # Animal.reply, Cat.speak
meow
>>> data = Hacker() # Animal.reply, Primate.speak
>>> data.reply()
Hello world!
¨ Class instances simulate objects in a domain
¨ NounsÞclasses, verbsÞmethods
¨ Class objects embed and activate other objects
¨ In OOP terminology: ‘has-a’ relationship
Example: the dead-parrot skit in Python
file: parrot.py
class Actor:
def line(self): print self.name + ':', `self.says()`
class Customer(Actor):
name = 'customer'
def says(self): return "that's one ex-bird!"
class Clerk(Actor):
name = 'clerk'
def says(self): return "no it isn't..."
class Parrot(Actor):
name = 'parrot'
def says(self): return None
class Scene:
def __init__(self):
self.clerk = Clerk() # embed some instances
self.customer = Customer() # Scene is a composite
self.subject = Parrot()
def action(self):
self.customer.line() # delegate to embedded
self.clerk.line()
self.subject.line()
% python
>>> import parrot
>>> parrot.Scene().action() # activate nested objects
customer: "that's one ex-bird!"
clerk: "no it isn't..."
parrot: None
¨ Everything is a first-class ‘object’
¨ Only objects derived from classes are OOP ‘objects’
¨ Classes can be passed around as data objects
def factory(aClass, *args): # varargs tuple
return apply(aClass, args) # call aClass
class Spam:
def doit(self, message):
print message
class Person:
def __init__(self, name, job):
self.name = name
self.job = job
object1 = factory(Spam) # make a Spam
object2 = factory(Person, "Guido", "guru") # make a Person
¨ Unbound class methods: call with a ‘self’
¨ Bound instance methods: instance + method pairs
object1 = Spam()
x = object1.doit # bound method object
x('hello world') # instance is implied
t = Spam.doit # unbound method object
t(object1, 'howdy') # pass in instance
Pseudo-private attributes
¨ Data hiding is a convention (until Python1.5 or later)
¨ “We’re all consenting adults” –Guido van Rossum
¨ 1.5 name mangling: “self.__X” Þ “self._Class__X”
¨ Class name prefix makes names unique in “self” instance
¨ Only works in class, and only if at most 1 trailing “_”
¨ Mostly for larger, multi-programmer, OO projects
class C1:
def meth1(self): self.__X = 88 # now X is mine
def meth2(self): print self.__X # becomes _C1__X in I
class C2:
def metha(self): self.__X = 99 # me too
def methb(self): print self.__X # becomes _C2__X in I
class C3(C1, C2): pass
I = C3() # two X names in I
I.meth1(); I.metha()
print I.__dict__
I.meth2(); I.methb()
% python private.py
{'_C2__X': 99, '_C1__X': 88}
88
99
Documentation strings
¨ Still not universally used
¨ Woks for classes, modules, functions, methods
· String constant before any statements
· Stored in object’s __doc__ attribute
file: docstr.py
"I am: docstr.__doc__"
class spam:
"I am: spam.__doc__ or docstr.spam.__doc__"
def method(self, arg):
"I am: spam.method.__doc__ or self.method.__doc__"
code...
def func(args):
"I am: docstr.func.__doc__"
code...
Classes versus modules
¨ Modules…
·
Are data/logic packages
·
Creation: files or extensions
·
Usage: imported
¨ Classes…
·
Implement new objects
·
Always live in a module
·
Creation: statements
·
Usage: called
OOP and Python
¨ Inheritance
· Based on attribute lookup: “X.name”
¨ Polymorphism
· In “X.method()”, the meaning of ‘method’ depends on the type (class) of ‘X’
¨ Encapsulation
· Methods and operators implement behavior; data hiding is a convention (for now)
class C:
def meth(self, x): # like x=1; x=2
…
def meth(self, x, y, z): # the last one wins!
…
class C:
def meth(self, *args):
if len(args) == 1:
…
elif type(arg[0]) == int:
…
class C:
def meth(self, x): # the python way:
x.operation() # assume x does the right thing
Python’s dynamic nature
¨
Members may be added/changed outside class methods
>>> class C: pass
...
>>> X = C()
>>> X.name = 'bob'
>>> X.job = 'psychologist'
¨ Scopes may be expanded dynamically: run-time binding
file: delayed.py
def printer():
print message # name resolved when referenced
% python
>>> import delayed
>>> delayed.message = "Hello" # set message now
>>> delayed.printer()
Hello
Subclassing builtin types in 2.2 (Advanced)
¨ All types now behave like classes: list, str, tuple, dict,…
¨ Subclass to custoimize builtin object behavior
¨ Alternative to writing “wrapper” code
# subclass
builtin list type/class
# map 1..N to
0..N-1, call back to built-in version
class
MyList(list):
def __getitem__(self, offset):
print '(indexing %s at %s)' % (self,
offset)
return list.__getitem__(self, offset -
1)
if __name__ ==
'__main__':
print list('abc')
x = MyList('abc') # __init__ inherited from list
print x # __repr__ inherited
from list
print x[1] # MyList.__getitem__
print x[3] # customizes list superclass
method
x.append('spam'); print x # attributes from list superclass
x.reverse(); print x
% python
typesubclass.py
['a', 'b', 'c']
['a', 'b', 'c']
(indexing ['a',
'b', 'c'] at 1)
a
(indexing ['a',
'b', 'c'] at 3)
c
['a', 'b', 'c',
'spam']
['spam', 'c',
'b', 'a']
“New Style” Classes in 2.2 (Advanced)
¨ Adds new feature, changes one inheritance case
¨ Only if “object”” or a builtin type as a superclass
¨ Py 3.0: all classes automatically new style, per BDFL
class newstyle(object):
…normal code…
¨ Adds: slots, limits legal attributes set
>>> class limiter(object):
... __slots__ = ['age', 'name', 'job']
...
>>> x.ape = 1000
AttributeError: 'limiter' object has no attribute
¨ Adds: properties, computed attributes alternative
>>> class classic:
... def __getattr__(self, name):
... if name == 'age':
... return 40
... else:
... raise AttributeError
...
>>> x = classic()
>>> x.age # runs __getattr__
40
>>> class newprops(object):
... def getage(self):
... return 40
...
age = property(getage, None, None, None)
# get,set,
...
>>> x = newprops()
>>> x.age # runs getage
40
¨ Adds: static and class methods, new calling patterns
class Spam: # static: no self
numInstances = 0 # class: class, not instance
def __init__(self):
Spam.numInstances += 1
def printNumInstances():
print "Number of instances:", Spam.numInstances
printNumInstances = staticmethod(printNumInstances)
>>> a = Spam()
>>> b = Spam()
>>> c = Spam()
>>> Spam.printNumInstances()
Number of instances: 3
¨ Function decorators (2.4+)
class C:
@staticmethod
def meth():
...
# is
equivalent to…
class C:
def meth():
...
meth = staticmethod(meth) # rebind name
@A @B @C
def f (): ...
# is
equivalent to…
def f(): ...
f = A(B(C(f)))
¨ Changes: behavior of “diamond” multiple inheritance
>>> class A: attr = 1 # classic
>>> class B(A): pass
>>> class C(A): attr = 2
>>> class D(B,C): pass # tries A before C
>>> x = D() # more depth-first
>>> x.attr
1
>>> class A(object): attr = 1 # new style
>>> class B(A): pass
>>> class C(A): attr = 2
>>> class D(B,C): pass # tries C before A
>>> x = D() # more breadth-first
>>> x.attr
2
¨ Multiple inheritance: order matters
· Solution: use sparingly and/or carefully
file: mi.py
class Super1:
def method2(self): # a 'mixin' superclass
print 'in Super1.method2'
class Super2:
def method1(self):
self.method2() # calls my method2??
def method2(self):
print 'in Super2.method2'
class Sub1(Super1, Super2):
pass # gets Super1's method2
class Sub2(Super2, Super1):
pass # gets Super2's method2
class Sub3(Super1, Super2):
method2 = Super2.method2 # pick method manually
Sub1().method1()
Sub2().method1()
Sub3().method1()
% python mi.py
in Super1.method2
in Super2.method2
in Super2.method2
¨ Wraps a Python list in each instance
¨ Supports multiple instances
¨ Adds operator overloading
¨ Supports customization by inheritance
¨ Allows any type of component: heterogeneous
file: set.py
class Set:
def __init__(self, value = []): # constructor
self.data = [] # manages a list
self.concat(value)
def intersect(self, other): # other is a sequence
res = [] # self is the subject
for x in self.data:
if x in other:
res.append(x)
return Set(res) # return a new Set
def union(self, other):
res = self.data[:] # copy of my list
for x in other:
if not x in res:
res.append(x)
return Set(res)
def concat(self, value): # value: list, Set...
for x in value: # removes duplicates
if not x in self.data:
self.data.append(x)
def __len__(self): return len(self.data)
def __getitem__(self, key): return self.data[key]
def __and__(self, other): return self.intersect(other)
def __or__(self, other): return self.union(other)
def __repr__(self): return 'Set:' + `self.data`
% python
>>> from set import Set
>>> x = Set([1,2,3,4]) # __init__
>>> y = Set([3,4,5])
>>> x & y, x | y # __and__,__or__,__repr__
(Set:[3, 4], Set:[1, 2, 3, 4, 5])
>>> z = Set("hello") # set of strings
>>> z[0] # __getitem__
'h'
>>> z & "mello", z | "mello"
(Set:['e', 'l', 'o'], Set:['h', 'e', 'l', 'o', 'm'])
¨ Class objects provide default behavior
· Classes support multiple copies, attribute inheritance, and operator overloading
· The class statement creates a class object and assigns it to a name
· Assignments inside class statements create class attributes, which export object state and behavior
· Class methods are nested defs, with special first arguments to receive the instance
¨ Instance objects are generated from classes
· Calling a class object like a function makes a new instance object
· Each instance object inherits class attributes, and gets its own attribute namespace
· Assignments to the first argument ("self") in methods create per-instance attributes
¨ Inheritance supports specialization
· Inheritance happens at attribute qualification time: on “object.attribute”, if object is a class or instance
· Classes inherit attributes from all classes listed in their class statement header line (superclasses)
· Instances inherit attributes from the class they are generated from, plus all its superclasses
· Inheritance searches the instance, then its class, then all accessible superclasses (depth-first, left-to-right)
Click here to go to
lab exercises
Click here to go to exercise
solutions
Click here to go to
solution source files
Click here to go to
lecture example files