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

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

 

 

 

 

 

 

 

 

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

 

 

 

 

 

 

 

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


 

 

 

 

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:

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


 

 

 

 

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

 

 

 

 

 

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” –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 builtin types in 2.2 (Advanced)

 

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

 

 

 

 

 

“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

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.

 

 

 

 

 

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