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
●
Search is DFLR
(except in diamonds), per order in class headers
●
Class objects:
supers and subs
●
Instance objects:
generated from 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
Why OOP?
●
OOP great at code
reuse, structure, and encapsulation
●
Program by customizing in new levels of hierarchy,
not changing
●
Extra structure of classes virtually required
once programs hit 1K lines (see frigcal)
●
Inheritance:
basis of specializing, customizing software—lower in tree means customization
●
Example: an
employee database app – company, departments, employees
●
Much more
complete than functional paradigm (though the two can often work together)
► Multiple Instances
► Specialization by inheritance
► Implementing operators
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
The world’s simplest Python class?
# an empty class, class and instance attrs filled in later
>>> 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 (but not typical)
>>> 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'
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"
Demo: A More Realistic Example
Using class instance objects as database records
Key ideas: __init__, methods, operators,
subclass, shelves
See Extras\Code\people for code, or
work along live
→ 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)”
♦ Search = DFLR, except for new-style BF in diamonds
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
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)
# See
also: super() built-in for generic superclass access
# But
this call has major issues in multiple-inheritance trees
# For
more details, see LP5E
and the last part of this
PDF
♦ 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
♦ 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
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-named 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, 2.X |
X == Y, X < Y |
__radd__ |
operator ‘+’ |
non-instance + X |
__iter__ |
iteration |
for item in X, I=iter(X) |
__next__ |
iteration |
next(I) |
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'
♦
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 4 9 16
♦
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
♦
iter
called on start of iterations, auto or
manual
♦
next
called to fetch each item along the way
>>> class squares:
... def __init__(self, start): # on squares()
... self.count = start
... def __iter__(self): # on iter()
... return self # or other object with state
... def __next__(self): # on next()
... if self.count == 1:
... raise StopIteration # end iteration
... else:
... self.count -= 1
... return self.count ** 2
...
>>> for i in squares(5): # automatic iterations
... print(i)
...
16
9
4
1
>>> S = squares(10) # manual iterations
>>> I = iter(S) # iter() optional if returns self
>>> next(I)
81
>>> next(I)
64
>>> list(I)
[49, 36, 25, 16, 9, 4, 1]
♦
Attribute
access management
■
setattr: partial
attribute privacy for Python classes
See Extras\Code\Misc\privates.py on
class CD
■ getattr:
full get/set attribute privacy for Python classes
See
Extras\Code\OOP\access.py
on class CD
♦ 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” –Python’s BDFL
♦ 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
♦ See __getattr__ above for implementing full privacy
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 (but very close!)
♦ 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
♦ All types now behave like classes: list, str, tuple, dict,…
♦ Subclass to customize 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']
♦ Adds new features, changes one inheritance case (diamonds)
♦ Py 2.X: only if “object” or a builtin type as a superclass
♦ Py 3.X: all classes automatically new style (“object” added auto)
class newstyle(object): # NS requires “object” in 2.X only
…normal code…
♦ Changes: behavior of “diamond” multiple inheritance
Per the linear MRO:
the DFLR path, with all but last appearance of each class removed
>>> 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
♦ Adds: slots, limits legal attributes set
Used to catch typos and limit memory requirements in
pathological cases (ONLY!)
>>> class limiter(object):
... __slots__ = ['age', 'name', 'job']
...
>>> x.ape = 1000
AttributeError: 'limiter' object has no attribute
♦ Adds: properties, computed attributes alternative
Used to route attribute access to databases,
approvals, special-case code
>>> 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,del
...
>>> x = newprops()
>>> x.age # <= runs getage
40
♦ Adds: static and class methods, new calling patterns
Used to process class data, instead of per-instance
date
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 and class decorators (not just newstyle)
Rebinds names to objects that process functions and
classes, or later calls to them
Function
name rebinding
@funcdecorator
def F():
...
# is
equivalent to…
def F():
...
F = funcdecorator(F) # rebind name, possibly to proxy object
Class
name rebinding
@classdecorator
class C:
...
# is
equivalent to…
class C:
...
C = classdecorator(C) # rebind name, possibly to proxy object
Use
for static methods (and properties, etc.)
class C:
@staticmethod
def meth():
...
# is
equivalent to…
class C:
def meth():
...
meth = staticmethod(meth) # rebind name to call handler
# ditto
for properties
class newprops(object):
@property
def age(self): # age = property(age)
return 40 # use X.age, not X.age()
Nesting:
multiple augmentations
@A
@B
@C
def f(): ...
# is
equivalent to…
def f(): ...
f = A(B(C(f)))
Arguments:
closures, retain state for later calls
@funchandler(a, b)
def F(…): …
# is
equivalent to…
def F(…): …
F = funchandler(a, b)(F)
For More Details…
● See the complete function
decorator and class decorator
examples in the top-level Extras\Code\OOP
● New-style inheritance algorithm, MRO, super(), metaclasses,
descriptors, decorator coding, … too much to cover here
● See the Advanced Topics section, this summary PDF on formal
inheritance rules and super(), and the book Learning Python.
♦ 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