Dies ist die Support Website des Buches:

Das Python Praxisbuch
Der große Profi-Leitfaden für Programmierer
Farid Hajji
Addison Wesley / Pearson Education
ISBN 978-3-8273-2543-3 (Sep 2008), 1298 Seiten.

10. Klassen und Objekte

Hello, OO-World!

#!/usr/bin/env python
# helloooworld.py -- Hello, OO-World!

class Hello(object):
    "Hello, OO-world!"

    def __init__(self, welcome):
        "Squirrel the welcome message away"
        self.welcome = welcome
        
    def hello(self):
        "Print the saved welcome message"
        print self.welcome

def main():
    "Play around with Hello"

    # Instantiate two Hello objects:
    hel1 = Hello("Hello, World!")
    hel2 = Hello("Good Bye, Cruel World!")

    # Call hel1 and hel2's hello method:
    hel1.hello()
    hel2.hello()

if __name__ == '__main__':
    main()

helloooworld.py

Klassen verwenden

Objekte instanziieren

Die leere Klasse C:

class C(object):
    pass

Die Klasse C2::

class C2(object):
    def __init__(self, magic):
        self.magic = magic

Objektattribute

Objektmethoden (Memberfunktionen)

Die Klasse C3:

class C3(object):
    def __init__(self):
        self.counter = 0
    def inc(self):
        self.counter = self.counter + 1

Die Klasse C4:

class C4(object):
    def __init__(self, initvalue=0):
        self.counter = initvalue
    def inc(self, increment=1):
        self.counter = self.counter + increment

Und die Klasse C5:

class C5(object):
    def __init__(self, initvalue=0):
        self.counter = initvalue
    def add(self, value):
        self.counter = self.counter + value
    def inc(self):
        self.add(1)   # Call another method

Klassen schreiben

#!/usr/bin/env python
# classdef.py -- Defining classes

class ObjectCounter(object):
    "A class that counts how many objects it created."
    
    nr_objects = 0
    
    def __init__(self, value=''):
        "Create a new object. Count it!"
        ObjectCounter.nr_objects = ObjectCounter.nr_objects + 1
        self.value = value
    
    def get_value(self):
        "Get the value of this object"
        return self.value
    
    def set_value(self, newvalue):
        "Change the value of this object"
        self.value = newvalue
    
    def object_count(self):
        "Return the number of ObjectCounter objects created so far."
        return ObjectCounter.nr_objects
    
    # This is a class method
    def override_object_count_cmethod(cls, newcount):
        print "Overriding %s.%d with %d" % (cls, cls.nr_objects, newcount)
        cls.nr_objects = newcount
    override_object_count_cmethod = classmethod(override_object_count_cmethod)
    
    # This is a static method
    def override_object_count_static(newcount):
        print "Overriding object count %d with %d" % (ObjectCounter.nr_objects,
                                                      newcount)
        ObjectCounter.nr_objects = newcount
    override_object_count_static = staticmethod(override_object_count_static)

classdef.py

Klassen aus Modulen importieren:

# 1. Either import just the names we want:
from classdef import ObjectCounter

obj_1 = ObjectCounter()
obj_1.get_value()

# 2. Or import the whole module:
import classdef

obj_1 = classdef.ObjectCounter(42)
obj_2 = classdef.ObjectCounter('hello')
obj_1.get_value()
obj_2.set_value(4711)

Klassenmethoden und statische Methoden

Die Klasse ObjectCounter mit classmethod:

class ObjectCounter(object):

    nr_objects = 0

    # Regular member functions omitted

    # This is a class method
    def override_object_count_cmethod(cls, newcount):
        print "Overriding %s.%d with %d" % (cls, cls.nr_objects, newcount)
        cls.nr_objects = newcount
    override_object_count_cmethod = classmethod(override_object_count_cmethod)

Eine weiterer ObjectCounter mit staticmethod:

class ObjectCounter(object):

    nr_objects = 0

    # Regular member functions omitted

    # This is a static method
    def override_object_count_static(newcount):
        print "Overriding object count %d with %d" % (ObjectCounter.nr_objects,
                                                      newcount)
        ObjectCounter.nr_objects = newcount
    override_object_count_static = staticmethod(override_object_count_static)

Klassenvererbung

Die Klassen Widget und Window:

class Widget(object):
    "A generic widget class"
    def __init__(self, value=''):
        self.value = value
    def get_value(self):
        return self.value

class Window(Widget):
    "A Window is a special Widget"
    def __init__(self):
        Widget.__init__(self, 'TheWindow')

Die Klasse Dialog:

class Dialog(Widget):
    "A Dialog is a special Widget"
    def __init__(self):
        super(Dialog, self).__init__('This is a Dialog')

Hooks

Ein Einführung in Hooks

Die Klasse object2:

class object2(object):
    def __str__(self):
        "Show object2 in a custom manner"
        return 'object2(0x%x)' % id(self)

Eine Tour der object-Hooks

#!/usr/bin/env python
# objecthooks.py -- instrument the class object by intercepting some hooks.

class object_i(object):
    '''An object with instrumented hooks.'''

    # __doc__ is a documentation string for the whole class
    __doc__ == 'An instrumented object'

    # __new__ is a class method for creating new instances
    def __new__(cls, *args, **kwargs):
        print "CALLED object_i.__new__(%s, %s, %s)" \
              % (cls, str(args), str(kwargs))
        return object.__new__(cls, args, kwargs)

    # The initializer (constructor)
    def __init__(self):
        print "CALLED object_i.__init__()"
        return super(object_i, self).__init__()

    # Called for del self.attrname
    def __delattr__(self, attrname):
        print "CALLED object_i.__delattr__(%s)" % (attrname,)
        return super(object_i, self).__delattr__(attrname) 

    # Called for self.attrname
    def __getattribute__(self, attrname):
        print "CALLED object_i.__getattribute__(%s)" % (attrname,)
        return super(object_i, self).__getattribute__(attrname)

    # Called for self.attrname = attrvalue
    def __setattr__(self, attrname, attrvalue):
        print "CALLED object_i.__setattr__(%s, %s)" % (attrname, attrvalue)
        return super(object_i, self).__setattr__(attrname, attrvalue)

    # Called for str(self)
    def __str__(self):
        print "CALLED object_i.__str__()"
        return 'object_i(0x%x)' % (id(self),)

    # Called for repr(self)
    def __repr__(self):
        print "CALLED object_i.__repr__()"
        return '<repr: object_i at 0x%x>' % (id(self),)

    # Called for hash(self)
    def __hash__(self):
        print "CALLED object_i.__hash__()"
        return super(object_i, self).__hash__()

objecthooks.py

Ein Dictionary mit case-insensitive Schlüsseln

#!/usr/bin/env python
# dictci.py -- dictionary with case-insensitive (string) keys.

class dictci(dict):
    '''Dictionary with case-insensitive (string) keys.'''
    
    __doc__ == 'A case insensitive dictionary'
    
    def __init__(self, mapping={}, *seq, **kwargs):
        for key, value in mapping.items():
            self.__setitem__(key.lower(), value)
        for key, value in seq:
            self.__setitem__(key.lower(), value)
        for key, value in kwargs.items():
            self.__setitem__(key.lower(), value)
    
    def __contains__(self, key):
        return super(dictci, self).__contains__(key.lower())
    
    def __delitem__(self, key):
        return super(dictci, self).__delitem__(key.lower())
    
    def __getitem__(self, key):
        return super(dictci, self).__getitem__(key.lower())
    
    def __setitem__(self, key, value):
        return super(dictci, self).__setitem__(key.lower(), value)

dictci.py

Ein Dictionary mit Default-Werten

#!/usr/bin/env python
# dictdefault.py -- dictionary with default value

class dictdefault(dict):
    '''Dictionary with default value.'''
    
    __doc__ == 'A dictionary with default value'
    
    def __init__(self, default=None, mapping={}, *seq, **kwargs):
        self.default = default
        
        for key, value in mapping.items():
            self.__setitem__(key, value)
        for key, value in seq:
            self.__setitem__(key, value)
        for key, value in kwargs.items():
            self.__setitem__(key, value)
    
    def __contains__(self, key):
        return True  # Every imaginable keys is there with default value!
    
    def __getitem__(self, key):
        try:
            return super(dictdefault, self).__getitem__(key)
        except KeyError:
            return self.default

dictdefault.py

Ein aufrufbares Objekt

Die Klassen TheAnswer und TheAdder:

class TheAnswer(object):
    def __call__(self):
        return 42

class TheAdder(object):
    def __call__(self, *args):
        return sum(args)

Die Klasse Counter:

class Counter(object):
    def __init__(self, start=0):
        self.start = start

    def __call__(self):
        self.start = self.start + 1
        return self.start

Propertys

#!/usr/bin/env python
# properties.py -- how to define properties.

class MyClass(object):
    def __init__(self, initval=0):
        print "Object created at 0x%x" % id(self)
        self._x = initval
        
    def getter(self):
        print "getter(0x%x) called" % id(self)
        return self._x

    def setter(self, value):
        print "setter(0x%x, %d) called" % (id(self), value)
        self._x = value

    def deleter(self):
        print "deleter(0x%x) called" % id(self)
        del self._x

    x = property(getter, setter, deleter, "I'm a managed attribute")

properties.py

#!/usr/bin/env python
# posint.py -- positive integers implemented as a property

class PosInt(object):
    "A positive integer"
    
    class InvalidValue(Exception):
        pass
    
    def __init__(self, i):
        if i <= 0:
            raise PosInt.InvalidValue("Only positive integers allowed")
        self._i = i
    
    def getter(self):
        return self._i
    
    def setter(self, value):
        if value <= 0:
            raise PosInt.InvalidValue("Only positive integers allowed")
        self._i = value
    
    def deleter(self):
        del self._i
    
    x = property(getter, setter, deleter, "A positive integer property")

posint.py

Deskriptoren

Die Klasse TimeDescriptor:

import time

class TimeDescriptor(object):
    def __get__(self, instance, owner):
        return time.time()
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass

Die Klasse TimeDemo:

class TimeDemo(object):
    thetime = TimeDescriptor()
    def __init__(self, somevalue):
        self.data = somevalue
#!/usr/bin/env python
# chattydescriptor.py -- A verbose descriptor class.

class ChattyDescriptor(object):
    "A chatty descriptor class"
    def __init__(self):
        self.store = {}

    def __get__(self, instance, owner):
        print "CALLED __get__(%s, %s, %s)" % \
              (str(self), str(instance), str(owner))
        try:
            value = self.store[instance]
            self.store[instance] = value + 1
            return value
        except KeyError:
            raise AttributeError("There is no such attribute")
    
    def __set__(self, instance, value):
        print "CALLED __set__(%s, %s, %s)" % \
              (str(self), str(instance), str(value))
        self.store[instance] = value
    
    def __delete__(self, instance):
        print "CALLED __delete__(%s, %s)" % \
              (str(self), str(instance))
        del self.store[instance]
    
    def keyhole(self):
        return self.store

chattydescriptor.py

Die Klasse ChattyDescriptorDemo:

class ChattyDescriptorDemo(object):
    "An example owner class for ChattyDescriptor"
    x = xmanager

__slots__

Das Problem:

alist = []

for i in range(1000):
    o = ChattyDescriptorDemo()
    o.x = "Object number %d" % (i,)
    alist.append(o)

Die Klasse RGB:

class RGB(object):
    def __init__(self, red, green, blue):
         self.red = red
        self.green = green
        self.blue = blue

Die Klasse RGB2:

class RGB2(object):

    __slots__ = ['red', 'green', 'blue']

    def __init__(self, r, g, b):
        self.red = r
        self.green = g
        self.blue = b

Metaklassen

Klassen sind Instanzen von Metaklassen

Die Klasse MyClass:

class MyClass(object):
    def __init__(self, initval=0):
        self.data = initval
    def show(self):
        print self.data

Die Klasse MyClass2 erzeugen:

def __init__(self, initval=0):
    self.data = initval

def show(self):
    print self.data

MyClass2 = type('MyClass2', (object,),
                {'__init__': __init__, 'show': show})

Die Metaklasse ChattyMetaClass:

class ChattyMetaClass(type):
    "A verbose meta class"
    def __new__(meta, name, bases, dict):
        print "ChattyMetaClass(%s, %s, %s)" % (name, str(bases), str(dict))
        return type.__new__(meta, name, bases, dict)

Daraus die Klasse MyClass3 erzeugen:

>>> MyClass3 = ChattyMetaClass('MyClass3', (object,),
...                            {'__init__': __init__, 'show': show})
ChattyMetaClass(MyClass3, (<type 'object'>,),
                {'__init__': <function __init__ at 0x284187d4>,
                 'show': <function show at 0x2841880c>})

Die Metaklasse MetaClassFactory:

def MetaClassFactory(name, bases, dict):
    "A factory function that cranks out classes"
    print "CALLED MetaClassFactory(%s, %s, %s)" % \
          (name, str(bases), str(dict))
    return type(name, bases, dict)

Draus die Klasse MyClass4 erzeugen:

>>> MyClass4 = MetaClassFactory('MyClass4', (object,),
...                             {'__init__': __init__, 'show': show})
CALLED MetaClassFactory(MyClass4, (<type 'object'>,),
                        {'__init__': <function __init__ at 0x284187d4>,
                         'show': <function show at 0x2841880c>})

Das __metaclass__-Attribut

Die Klasse MyClass5:

class MyClass5(object):
    "A class created from a custom meta class"

    __metaclass__ = ChattyMetaClass

    def __init__(self, initvalue=0):
        self.data = initvalue
    def show(self):
        print self.data

Die Klasse MyClass6:

class MyClass6(object):
    "A class created from a custom class factory"

    __metaclass__ = MetaClassFactory

    def __init__(self, initvalue=0):
        self.data = initvalue
    def show(self):
        print self.data

Die Klasse MyDerivedClass:

class MyDerivedClass(MyClass5):
    def show(self):
        print "derived",
        super(MyDerivedClass, self).show()

Anwendungen von Metaklassen

Klassen umbenennen

Die Metaklasse RenameMetaClass:

class RenameMetaClass(type):
    "A meta class that renames classes by prepending a prefix"
    def __new__(meta, name, bases, dict):
        return type.__new__(meta, 'My' + name, bases, dict)

Die Klassen A und B:

class A(object):
    __metaclass__ = RenameMetaClass

class B(object):
    __metaclass__ = RenameMetaClass

Die Klasse D:

class D(A, B):
    pass

Mixin-Klassen als Metaklassen

Die Mixin-Klasse MyMixin:

class MyMixin:
    def foo(self):
        print "foo() from Mixin:", self.fooval
    def bar(self):
        print "bar() from Mixin:", self.barval

Die Klasse MyClass:

class MyClass(object, MyMixin):
    def __init__(self, fooval, barval):
        self.fooval = fooval
        self.barval = barval
    def foobar(self):
        print "foobar() from MyClass:", self.fooval, self.barval

Es geht auch so:

def foometa(self):
    print "foo() from Metamixin:", self.fooval

def barmeta(self):
    print "bar() from Metamixin:", self.barval

class MyMetaMixin(type):
    def __new__(meta, name, bases, dict):
        dict.update({'foo': foometa, 'bar': barmeta})
        return type.__new__(meta, name, bases, dict)

Daraus erzeugen wir MyClass2:

class MyClass2(object):

    __metaclass__ = MyMetaMixin

    def __init__(self, fooval, barval):
        self.fooval = fooval
        self.barval = barval

    def foobar(self):
        print "foobar() from MyClass2:", self.fooval, self.barval

Weitere Anwendungen von Metaklassen

Zusammenfassung