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



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



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


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


>>> y.method()        # run method to process y


>>> rec.method(x)     # can call through instance or class









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





      An object (and statement) which defines inherited members and methods


      Objects created from a class, which inherit its attributes; each instance is a new namespace


      An attribute of a class or instance object, that’s bound to an object


      An attribute of a class object, that’s bound to a function object (a callable member)


      By convention, the name given to the implied instance object in methods


      When an instance or class accesses a class’s attributes


      Class or classes another class inherits attributes from


      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





      “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










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



class Replacer(Super):

    def method(self):

        print 'in Replacer.method'


class Extender(Super):

    def method(self):

        print 'starting Extender.method'


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


    print '\nProvider...'




% python specialize.py


in Super.method



in Replacer.method



starting Extender.method

in Super.method

ending Extender.method



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












Common operator overloading methods



     Special method names have 2 “_” before and after

     See Python manuals or reference books for the full set





Called for



object creation: X()



object reclamation


operator ‘+’

X + Y


operator ‘|’

X | Y



print X, `X`


function calls







X[key], iteration, in



X[key] = value



len(X), truth tests


Comparison, 2.X

X == Y, X < Y


operator ‘+’

non-instance + X



for item in X, I=iter(X)












     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__





     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)






>>> S = squares(10)        # manual iterations

>>> I = iter(S)            # iter() optional if returns self

>>> next(I)


>>> next(I)


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




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








     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


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












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







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



def func(args):

    "I am: docstr.func.__doc__"





Classes versus modules




     Are data/logic packages

     Creation: files or extensions

     Usage: imported



     Implement new objects

     Always live in a module

     Creation: statements

     Usage: called






OOP and Python




      Based on attribute lookup: “X.name”


      In “X.method()”, the meaning of ‘method’ depends on the type (class) of ‘X’


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





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)


(indexing ['a', 'b', 'c'] at 3)


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



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





     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__



>>> class newprops(object):

...     def getage(self):

...         return 40

...     age = property(getage, None, None, None)  # get,set,del


>>> x = newprops()

>>> x.age                          # <= runs getage





     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



def F():



# is equivalent to…


def F():


F = funcdecorator(F)   # rebind name, possibly to proxy object



Class name rebinding



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:


   def meth():



# is equivalent to…


class C:

   def meth():


   meth = staticmethod(meth)   # rebind name to call handler


# ditto for properties


class newprops(object):


    def age(self):     # age = property(age)

        return 40      # use X.age, not X.age()



Nesting: multiple augmentations





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









% 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



    def intersect(self, other):        # other is a sequence

        res = []                       # self is the subject

        for x in self.data:

            if x in other:


        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:


        return Set(res)


    def concat(self, value):           # value: list, Set...

        for x in value:                # removes duplicates

           if not x in self.data:



    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__


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