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

  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.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'

  1. 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

  1. 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:

  1. Instance → assignments to �self� attributes

  2. Class → statements (assignments) in class statements

  3. 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:

  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!

��

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

Click here to go to solution source files

Click here to go to lecture example files