7. Classes

 

 

 

 

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

·        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

 

 

 

 

A first look: class basics

 

 

 

 

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

 

 

 

 

 

 

 

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)”  (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

 

 

 

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

 

 

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)

 

 

 

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

 

 

 

 

  class S1:

  

  class S2:

 
 

 

 

 

 


 

  class X(S1, S2):

        def attr(self,…):

              self.attr = V

 

  object = X()

 
 

 

 

 

 

 

 

 

 

 

 

object.attr ?

 
 

 

 

 

 

 


 


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:

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


 

 

 

 

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


 

 

 

 

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 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}


 

 

 

 

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

 

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

 

  

 

 

 

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” –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,del

...

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

 

 

 

 

Class gotchas

 

 

 

¨  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

 


 

 

 

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)

 

    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'])


 

 

 

 

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