Learning Python: Unit 7, Classes (original) (raw)
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
OOP: The Big Picture
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)
A first look: class basics
► Multiple Instances
► Specialization by inheritance
► Implementing operators
- 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.datadiffers 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 structin 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'
- 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)���������� # setdatafound in FirstClass
>>> z.display()������������ # finds/calls overridden method
Current value = "42"
>>> x.display()������������ # x is a FirstClassinstance
New value
- 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
A closer look: class terminology
→ 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
Using the class statement
♦ 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 (superclass,�):�������� # assign to name
��� data = value������������������� # shared class data
��� defmethod(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
��� defdisplay(self):
������� print self.data,Subclass.data����# instance, class
>>> x, y = Subclass(1), Subclass(2)
>>> x.display();y.display()
1 spam
2 spam
Using class methods
♦ �_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 LP5Eand the last part of this PDF
Customization via inheritance
♦ 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:
Instance → assignments to �self� attributes
Class → statements (assignments) in class statements
Superclasses → classes listed in parenthesis in header
Specializing inherited methods
♦ Inheritance finds names in subclass before superclass
♦ Subclasses may inherit, replace, extend, or provide
♦ Direct superclass method calls: Class.method(self,�)
>>> class Super:
...���� defmethod(self):
...�������� print 'in Super.method'
...
>>> class Sub(Super):
...���� defmethod(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
Operator overloading in classes
♦ 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������������� # initdata
...���� 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
SeeExtras\Code\OOP\access.pyon class CD
Namespace rules: the whole story
♦ 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 localscope, then the current global scope, then the outer built-inscope
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}
Optional reading: OOP and inheritance
��
♦ Inheritance based on attribute qualification
♦ In OOP terminology: �is-a� relationship
♦ On �X.name�, looks for �name� in:
_Instance_������������ ←X�s own name-space
Class ��������������� ←class that X was made from
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!
��
Optional reading: OOP and composition
��
♦ 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
�����
��
��
�Classes are objects: factories
♦ Everything is a first-class �object�
♦ Only objects derived from classes are OOP �objects�
♦ Classes can be passed around as data objects
deffactory(aClass, *args):������� # varargstuple
��� 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
��
Methods are objects: bound or unbound
♦ _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
Odds and ends
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
Subclassing builtintypes in 2.2 (Advanced)
♦ All types now behave like classes: list, str, tuple, dict,�
♦ Subclass to customize builtinobject 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 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
defF():
�� ...
# is equivalent to�
defF():
�� ...
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
deff(): ...
# is equivalent to�
�
deff(): ...
f = A(B(C(f)))
Arguments: closures, retain state for later calls
@funchandler(a, b)
defF(�): �
# is equivalent to�
defF(�): �
F = funchandler(a, b)(F)
For More Details�
● See the complete function decorator and class decoratorexamples 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.
�
Class gotchas
♦ Multiple inheritance: order matters
● Solution: use sparingly and/or carefully
file: mi.py
class Super1:
��� defmethod2(self):������������ # a 'mixin' superclass
������� print 'in Super1.method2'
class Super2:
��� defmethod1(self):
�������self.method2()������������ # calls my method2??
��� defmethod2(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
Optional reading: a set class
♦ 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)
��� defintersect(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'])
Summary: OOP in Python
♦ 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)
Lab Session 6
Click here to go to lab exercises
Click here to go to exercise solutions