Objekt-Modell von Python 3¶
Ein Teil der folgenden Beispiele stammt aus dem Buch „Python Essential Reference“ von David M. Beazley und werden mit freundlicher Erlaubnis des Autors in teilweise angepasster Form genutzt.
Der Attribut-Zugriff erfolgt nach einem speziellen Algorithmus.
Klassendefinition¶
# -*- coding: utf8 -*-
import gc
# Klassendefinition
class Account(object):
# Ableitung von object kann bei Python 3 entfallen:
# class Account:
# Klassenvariable
num_accounts = 0
# Initialisierer; de facto kein Konstruktor, da das Objekt hier schon
# existiert
def __init__(self, name, balance):
self.name = name
self.balance = balance
Account.num_accounts += 1
# "Finalisierer" (de facto kein Destruktor); wird erst beim Zerstören des
# Objekts gerufen, also wenn der Referenzzähler auf 0 sinkt; bei zyklischen
# Referenzen werden Objekte mit __del__() vor Python 3.4 nicht zerstört; es
# ist nicht garantiert, dass __del__() für Objekte gerufen wird, die beim
# Beenden des Interpreters noch existieren
#
# siehe auch
# https://docs.python.org/3/reference/datamodel.html#object.__del__
#
# https://docs.python.org/3/whatsnew/3.4.html#pep-442-safe-object-finalization
#
# PEP 442 removes the current limitations and quirks of object
# finalization in CPython. With it, objects with __del__() methods, as
# well as generators with finally clauses, can be finalized when they
# are part of a reference cycle.
def __del__(self):
Account.num_accounts -= 1
print('__del__', self.name)
def deposit(self, amt):
self.balance = self.balance + amt
def withdraw(self, amt):
self.balance = self.balance - amt
def inquiry(self):
return self.balance
if __name__ == '__main__':
# Instanzen
# einige Accounts anlegen
a = Account('Guido', 1000.00) # ruft Account.__init__(a, 'Guido', 1000.00)
b = Account('Bill', 10.00)
c = Account('Susan', 4000.00)
d = Account('Mary', 500.00)
# Attributzugriff
print(Account.num_accounts)
print(Account.__init__)
print(Account.__del__)
print(Account.deposit)
print(Account.withdraw)
print(Account.inquiry)
a.deposit(100.00) # ruft Account.deposit(a, 100.00)
b.withdraw(50.00) # ruft Account.withdraw(b, 50.00)
name = a.name # den Account-Namen ermitteln
print(name, a.balance)
print(b.name, b.inquiry())
# Objekte zerstören
del a
print('a deleted') # __del__() wird gerufen
b2 = b # Referenzzähler wird erhöht
del b
print('b deleted') # __del__() wird nicht gerufen, da b2 noch auf das Objekt verweist
# zirkuläre Referenz erstellen
c.x = d
d.x = c
del c, d
gc.collect() # den Garbage Collector gezielt rufen
print('***')
Sichtbarkeitsregeln im Klassen-Körper¶
# spezielle Sichtbarkeitsregeln im Klassen-Körper
a = 'global a'
class X():
# außerhalb von Methoden erfolgt der Zugriff auf Attribute der Klasse über
# einfache Namen; in Methoden sind dagegen für Klassen- und Instanz-Attribute
# immer voll qualifizierte Namen zu verwenden
a = 'a in class'
b = a.replace('a ', 'b ')
# X.a statt a ist nicht zulässig, da X nicht sichtbar ist
#
# NameError: name 'X' is not defined
def hello(self):
print('hello ', self)
def print_vars(self, my = a): # hier ist X.a als a sichtbar
print(my) # a in class
try:
hello(1) # hello() ist nicht sichtbar
except Exception as e:
print(repr(e)) # NameError("name 'hello' is not defined",)
X.hello(2) # X.hello() ist sichtbar
print(self.a) # a in class
print(X.a) # a in class
print(a) # global a
print(self.b) # b in class
print(b) # NameError: name 'b' is not defined
# im Klassenkörper ist hello() sichtbar
hello(3)
x = X()
x.print_vars()
Vererbung (inheritance)¶
# Vererbung (inheritance)
import random
from classdef import Account
class SimpleEvilAccount(Account):
fake = False
def inquiry(self):
# das Fake-Flag könnte man zufällig setzen, z.B. so:
# self.fake = random.randint(0, 4) == 1
# wir wollen das Fake-Flag immer negieren; beim ersten Ruf der Methode
# inquiry() wird durch den Zugriff auf self.fake die Klassenvariable
# SimpleEvilAccount.fake ausgelesen und deren Inhalt in die neu
# erstellte Instanz-Variable self.fake übernommen; später wird stets
# die Instanz-Variable negiert
self.fake = not self.fake
return self.balance * 1.10 if self.fake else self.balance
class EvilAccount(SimpleEvilAccount):
def __init__(self, name, balance, evilfactor):
super().__init__(name, balance) # den Account initialisieren
self.evilfactor = evilfactor
def inquiry(self):
self.fake = not self.fake
return self.balance * self.evilfactor if self.fake else self.balance
class MoreEvilAccount(EvilAccount):
def deposit(self, amount):
self.withdraw(5.00) # Gebühr abziehen
super().deposit(amount) # Einzahlung ausführen
# EvilAccount.deposit(self, amount) # Realisierung ohne das empfohlene super()
if __name__ == '__main__':
print(MoreEvilAccount.__mro__)
for c in (SimpleEvilAccount('George', 1000.00),
EvilAccount('George Evil', 1000.00, 1.2),
MoreEvilAccount('George More Evil', 1000.00, 1.3)):
c.deposit(10.0) # ruft am Ende Account.deposit(c, 10.0)
for _ in range(3): print(c.inquiry())
print(isinstance(c, Account))
print('---')
Mehrfachvererbung (multiple inheritance)¶
# Mehrfachvererbung (multiple inheritance)
from inheritance import EvilAccount
class number():
def __init__(self, number):
self.number = number
def add(self, amt):
print('number.add', amt)
self.number += amt
# Mixin-Klasse
class addsub():
def add(self, amt):
# überschreibe number.add()
print('addsub.add', amt)
# rufe das überschriebene number.add()
super().add(amt)
def sub(self, amt):
print('addsub.sub', amt)
self.add(-amt)
class counter(addsub, number): pass
print(counter.__mro__)
n = number(10)
n.add(5) # ruft number.add(n, 5)
print(n.number)
print('---')
c = counter(100)
c.add(10) # ruft addsub.add(c, 10)
c.sub(5)
print(c.number)
print('---')
# Mixin-Klasse
class DepositCharge(object):
fee = 5.00
def deposit_fee(self, amt, fee = fee):
# fee = fee übernimmt den Wert von DepositCharge.fee
print('DepositCharge deposit_fee', amt, fee, self.fee)
self.deposit(amt, fee)
# Mixin-Klasse
class WithdrawCharge(object):
fee = 2.50
def withdraw_fee(self, amt, fee = fee):
# fee = fee übernimmt den Wert von WithdrawCharge.fee
print('WithdrawCharge withdraw_fee', amt, fee, self.fee)
self.withdraw(amt, fee)
# Klasse, die Mehrfachvererbung nutzt (normalerweise nur für die Einbindung von
# Mixins empfehlenswert)
class MostEvilAccount(EvilAccount, DepositCharge, WithdrawCharge):
def deposit(self, amt, fee = 0):
print('MostEvilAccount deposit', amt, fee, self.fee)
super().deposit(amt - fee)
def withdraw(self, amt, fee = 0):
print('MostEvilAccount withdraw', amt, fee, self.fee)
super().withdraw(amt + fee)
d = MostEvilAccount('Dave', 500.00, 1.10)
d.deposit_fee(20) # ruft DepositCharge.deposit_fee()
d.withdraw_fee(10) # ruft WithdrawCharge.withdraw_fee()
# self.fee hat wegen der MRO in beiden Fällen den Wert 5.00 (!);
# durch "fee = fee" bei deposit_fee() und withdraw_fee()
# wird der Wert der jeweiligen Klassenvariablen verwendet
for _ in range(2): print(d.inquiry())
print()
for cls in MostEvilAccount.__mro__: print(cls)
# <class '__main__.MostEvilAccount'>
# <class 'evilaccount.EvilAccount'>
# <class 'evilaccount.SimpleEvilAccount'>
# <class 'account.Account'>
# <class '__main__.DepositCharge'>
# <class '__main__.WithdrawCharge'>
# <class 'object'>
# ggf. ist keine MRO bestimmbar
class X(): pass
class Y(X): pass
class Z(X, Y): pass
# TypeError: Cannot create a consistent method resolution
# order (MRO) for bases X, Y
Mixin-Beispiel - ForkingMixIn¶
XML-RPC-Server
#!/usr/bin/env python3
# -*- coding: utf8 -*-
"""
Demo für die Mixin-Nutzung:
- SocketServer stellt ForkingMixIn und ThreadingMixIn bereit
- wir nutzen hier das ForkingMixIn beim SimpleXMLRPCServer
"""
from __future__ import print_function
import sys
try:
from xmlrpc.server import SimpleXMLRPCServer # Python 3
from socketserver import ForkingMixIn
py3 = True
except ImportError: # Python 2
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SocketServer import ForkingMixIn
py3 = False
class MyXMLRPCServer(ForkingMixIn, SimpleXMLRPCServer):
def verify_request(self, request, client_address):
host, port = client_address
if host != '127.0.0.1':
print('invalid client', host, file = sys.stderr)
return False
if py3:
return super().verify_request(request, client_address)
# MyXMLRPCServer ist wegen seiner Basisklassen bei Python 2 eine old-style
# class; daher funktioniert super() nicht und wir referenzieren die
# Basisklasse über deren Namen und müssen den Parameter self übergeben
return SimpleXMLRPCServer.verify_request(self, request, client_address)
# vom XML-RPC-Server bereitgestellte Additions-Funktion
def add(x, y):
return x + y
server = MyXMLRPCServer(('', 45000))
server.register_function(add)
server.serve_forever()
XML-RPC-Klient
#!/bin/env python3
import xmlrpc.client, sys
# bei Python 2 ist xmlrpclib zu importieren
host = sys.argv[1] if len(sys.argv) > 1 else 'localhost'
s = xmlrpc.client.ServerProxy('http://%s:45000' % host)
print(s.add(3, 4))
Polymorphie, Dynamische Bindung und Duck Typing¶
# Polymorphie, Dynamische Bindung und Duck Typing
# https://en.wikipedia.org/wiki/Duck_typing#Example
class Parrot:
def fly(self): print('Parrot flying')
class Airplane:
def fly(self): print('Airplane flying')
class Whale:
def swim(self): print('Whale swimming')
def lift_off(entity): entity.fly()
parrot = Parrot()
airplane = Airplane()
whale = Whale()
lift_off(parrot) # Parrot flying
lift_off(airplane) # Airplane flying
lift_off(whale) # Ausnahme: 'Whale' object has no attribute 'fly'
Statische und Klassenmethoden¶
# Statische und Klassenmethoden
import time
class Foo:
@staticmethod
def add(x, y):
return x + y
print(Foo.add(3, 4)) # 7
print('---')
class Times:
factor = 1
@classmethod
def mul(cls, x):
return cls.factor * x
class TwoTimes(Times):
factor = 2
print(TwoTimes.mul(4)) # ruft logisch Times.mul(TwoTimes, 4) -> 8
# reale alternative Rufmöglichkeiten
# TwoTimes.mul ==> <bound method Times.mul of <class '__main__.TwoTimes'>>
v = Times.__dict__['mul'] # <classmethod object at 0x...>
f = type(v).__get__(v, None, TwoTimes) # <bound method Times.mul of <class '__main__.TwoTimes'>>
print(f(4))
# Times.__dict__['mul'].__get__(TwoTimes()) ==>
# <bound method Times.mul of <class '__main__.TwoTimes'>>
print(Times.__dict__['mul'].__get__(TwoTimes())(4))
# Times.__dict__['mul'].__func__ ==>
# <function Times.mul at 0x7fcf531479d8>
print(Times.__dict__['mul'].__func__(TwoTimes, 4))
print('---')
class Date(object):
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __str__(self):
# String-Darstellung einer Date-Instanz
return '%04d-%02d-%02d' % (self.year, self.month, self.day)
@staticmethod
def now():
t = time.localtime()
return Date(t.tm_year, t.tm_mon, t.tm_mday)
@staticmethod
def tomorrow():
t = time.localtime(time.time() + 86400) # 86400 Sekunden (1 Tag) addieren
return Date(t.tm_year, t.tm_mon, t.tm_mday)
@classmethod
def cnow(cls):
t = time.localtime()
# kreiert ein Objekt vom passenden Typ (cls)
return cls(t.tm_year, t.tm_mon, t.tm_mday)
class EuroDate(Date):
# überschreibt Date.__str__(), liefert ein europäisches Datumsformat
def __str__(self):
return '%02d/%02d/%4d' % (self.day, self.month, self.year)
# einige Datumsobjekte erstellen
a = Date(1967, 4, 9)
e = EuroDate(1967, 4, 9)
n = Date.now() # ruft die statische Methode now()
t = Date.tomorrow() # ruft die statische Methode tomorrow()
D = e.now() # ruft Date.now() und liefert ein Date-Objekt
A = a.cnow() # ruft logisch Date.cnow(Date) und liefert ein Date-Objekt
E = e.cnow() # ruft logisch Date.cnow(EuroDate) und liefert ein EuroDate-Objekt
# real nutzbarer Ruf der classmethod cnow
print(Date.__dict__['cnow'].__func__(Date))
print(Date.__dict__['cnow'].__func__(EuroDate))
print('---')
for x in a, e, n, t, D, A, E:
print(x, vars(x), type(x))
print('---')
Properties¶
# Properties
#
# dabei handelt es sich um spezielle Deskriptoren
import math
class Circle(object):
def __init__(self, radius):
self.radius = radius
# einige zusätzliche Eigenschaften von Circle-Objekten
@property
def area(self):
# Kreisfläche: π * r²
return math.pi * self.radius ** 2
@property
def perimeter(self):
# Kreisumfang: 2 * π * r
return 2 * math.pi * self.radius
c = Circle(4.0)
print(c.radius) # 4.0
print(c.area) # 50.26548245743669
print(c.perimeter) # 25.132741228718345
# c.area = 2
# Traceback (most recent call last):
# File '<stdin>', line 1, in <module>
# AttributeError: can't set attribute
print(Circle.area) # <property object at 0x...>
print(type(Circle.area)) # <class 'property'>
print(type(Circle.__init__)) # <class 'function'>
print(Circle.__init__) # <function Circle.__init__ at 0x...>
print(type(c.__init__)) # <class 'method'>
print(c.__init__) # <bound method Circle.__init__ of <__main__.Circle object at 0x...>>
# Hinweis:
# der Zugriff auf das Methoden-Objekt __init__ über die Instanz (c.__init__)
# liefert kein Funktions-Objekt, sondern eine gebundene Methode (bound method),
# da alle Funktionen eine Methode __get__() haben und so als Deskriptor wirken;
# eine Methode könnte man hier auch als eine Art Property verstehen
#
# siehe auch https://docs.python.org/3.6/howto/descriptor.html#functions-and-methods
class Foo(object):
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('Must be a string!')
self.__name = value
@name.deleter
def name(self):
raise TypeError("Can't delete name")
f = Foo('Guido')
f.name = 'Monty' # ruft setter name(f, 'Monty')
print(f.name) # ruft getter name()
members = frozenset(dir(f))
print('members', members)
try:
f.name = 45 # ruft setter name(f, 45) -> TypeError
del f.name # ruft deleter name(f) -> TypeError
except Exception as e:
print(e)
print('---')
# älterer Code mit property() statt @property
#
# bei der Dekorator-Variante (@property) sind die get/set/delete-Funktionen
# nicht als Methoden der Klasse sichtbar
class Foo(object):
def __init__(self, name):
self.__name = name
def getname(self):
return self.__name
def setname(self, value):
if not isinstance(value, str):
raise TypeError('Must be a string!')
self.__name = value
def delname(self):
raise TypeError("Can't delete name")
name = property(getname, setname, delname)
members2 = frozenset(dir(Foo('Max')))
print(members - members2) # frozenset()
print(members2 - members) # frozenset({'setname', 'getname', 'delname'})
Descriptoren (descriptors)¶
# Descriptoren (descriptors)
#
# https://docs.python.org/3.6/howto/descriptor.html
from weakref import WeakKeyDictionary
# ein Deskriptor ist ein Objekt, das den Wert eines Attributs repräsentiert; es
# implementiert eine oder mehrere der Methoden __get__(), __set__() und
# __delete__(); Deskriptoren sind nur auf Klassen-Ebene instanziierbar, nicht
# auf der Instanz-Ebene
class TypedProperty(object):
def __init__(self, name, typ, default = None):
self.name = '_' + name
self.type = typ
self.default = typ() if default is None else default
def __get__(self, instance, cls):
# wird __get__() des Deskriptor-Objekts für die Instanz None gerufen,
# dann wird eine Referenz auf das Deskriptor-Objekts selbst (self)
# zurückgegeben; siehe auch
# https://docs.python.org/3.6/howto/descriptor.html#properties
# Properties liefern bei __get__() für Objekt None ebenfalls self
return getattr(instance, self.name, self.default) if instance else self
def __set__(self, instance, value):
if not isinstance(value, self.type):
raise TypeError('Must be a %s' % self.type)
setattr(instance, self.name, value)
def __delete__(self, instance):
raise AttributeError("Can't delete attribute")
class Foo(object):
name = TypedProperty('name', str)
num = TypedProperty('num', int, 42)
# https://www.smallsurething.com/python-descriptors-made-simple/
class Price(object):
def __init__(self):
self.default = 0
self.values = WeakKeyDictionary()
def __get__(self, instance, owner):
return self.values.get(instance, self.default)
def __set__(self, instance, value):
if value < 0 or value > 100:
raise ValueError('Price must be between 0 and 100.')
self.values[instance] = value
def __delete__(self, instance):
del self.values[instance]
class Book(object):
# den Preis eines Buches speichern wir zentral im Deskriptor-Objekt
price = Price()
def __init__(self, author, title, price):
self.author = author
self.title = title
self.price = price
def __str__(self):
return '{0} - {1}'.format(self.author, self.title)
if __name__ == '__main__':
f = Foo()
f.name = 'Guido' # ruft Foo.name.__set__(f, 'Guido')
print(f.name) # ruft implizit Foo.name.__get__(f, Foo)
print(Foo.name.__get__(f, Foo))
# <bound method TypedProperty.__get__ of <__main__.TypedProperty object at 0x...>>
print(f.num) # 42 (default)
f.num = 88
print(f.num) # 88
try:
del f.name # ruft Foo.name.__delete__(f)
except Exception as e:
print(e) # Can't delete attribute
b1 = Book('William Faulkner', 'The Sound and the Fury', 12)
b2 = Book('John Dos Passos', 'Manhattan Transfer', 13)
b3 = Book('George Orwell', '1984', 14)
print(b1.price, b2.price, b3.price)
# direkter Zugriff auf das Deskriptor-Objekt vom Typ Price
books = b1.__class__.__dict__['price'].values
# Book b2 löschen
del b2
# Kontrollausgabe des Inhalts des WeakKeyDictionarys (b2 ist bereits
# entfernt)
print(books.data)
# das WeakKeyDictionary durchlaufen und die Books ausgeben
for book in books:
print('%s: %s ; %s' % (book.author, book.title, books[book]))
Datenkapselung und private Attribute¶
# Datenkapselung und private Attribute
class A(object):
def __init__(self):
self.__X = 3 # wird zu to self._A__X
def __spam(self): # wird zu _A__spam()
print('A.__spam', vars(self))
def spam(self):
print('A.spam', vars(self))
def bar(self):
self.__spam() # ruft nur A.__spam()
self.spam() # ruft A.spam() oder B.spam()
class B(A):
def __init__(self):
A.__init__(self)
self.__X = 37 # wird zu self._B__X
def __spam(self): # wird zu _B__spam()
print('B.__spam', vars(self))
def spam(self):
print('B.spam', vars(self))
# über diese privaten Namen (name mangling) kann eine Superklasse verhindern,
# dass eine abgeleitete Klasse die Implementation der Methode ändert; A.bar()
# ruft generell A.__spam(), unabhängig vom Typ von self oder der Existenz der
# Methode __spam() in der abgeleiteten Klasse
A().bar()
B().bar()
Objekt-Erzeugung¶
# Objekt-Erzeugung
class Circle(object):
def __init__(self, radius):
self.radius = radius
# einige Circle-Instanzen schaffen
c = Circle(4.0)
d = Circle(5.0)
# dabei wird normalerweise von der Methode __call__() der Metaklasse zuerst
# __new__() und dann __init__() gerufen, sofern von __new__() ein Objekt vom
# erwarteten Typ (hier Circle) geliefert wurde
#
# siehe auch:
#
# https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/#putting-it-all-together
#
e = Circle.__new__(Circle, 6.0)
if isinstance(c, Circle):
Circle.__init__(e, 6.0) # dieser Wert ist hier relevant für self.radius
for x in c, d, e:
print(vars(x))
# bei der Ableitung von einem unveränderlichen Typ kann man in __new__()
# Einfluss auf den Wert nehmen; außerdem spielt __new__() typischerweisen in
# Metaklassen eine Rolle
class Upperstr(str):
def __new__(cls,value=''):
return str.__new__(cls, value.upper())
u = Upperstr('hello') # u = 'HELLO'
Referenz-Zähler und Garbage Collection¶
# Referenz-Zähler und Garbage Collection
import sys, gc, weakref
# Realisierung des Observer Patterns: Implementierung ohne weakref
class Account(object):
def __init__(self, name, balance):
self.name = name
self.balance = balance
self.observers = set()
def __del__(self):
print('Account: del called for', self)
for ob in self.observers:
ob.close()
del self.observers
def register(self,observer):
self.observers.add(observer)
def unregister(self,observer):
self.observers.remove(observer)
def notify(self):
for ob in self.observers:
ob.update()
def withdraw(self,amt):
self.balance -= amt
self.notify()
class AccountObserver(object):
def __init__(self, theaccount):
self.theaccount = theaccount
theaccount.register(self)
def __del__(self):
print('AccountObserver: del called for', self)
self.theaccount.unregister(self)
del self.theaccount
def update(self):
print('Balance is %0.2f' % self.theaccount.balance)
def close(self):
print('Account no longer in use')
# Observer Pattern: Implementierung mit weakref (besser)
class AccountObserver2(object):
def __init__(self, theaccount):
self.accountref = weakref.ref(theaccount) # eine weakref erzeugen
theaccount.register(self)
def __del__(self):
acc = self.accountref() # den Account über die weakref holen
if acc: # den Observer austragen, wenn der Account noch existiert
acc.unregister(self)
def update(self):
print('Balance is %0.2f' % self.accountref().balance)
def close(self):
print('Account no longer in use')
a = Account('Dave', 1000.00)
a_ob = AccountObserver(a)
a2 = Account('Dave', 1500.00)
a_ob2 = AccountObserver2(a2)
del a, a_ob
gc.collect()
print('\nnach a, a_ob und gc.collect\n')
del a2, a_ob2
gc.collect()
print('nach a2, a_ob2 und gc.collect\n')
# Ausgabe:
#
# Account: del called for <__main__.Account object at 0x...>
# Account no longer in use
# AccountObserver: del called for <__main__.AccountObserver object at 0x...>
# Exception ignored in: <bound method AccountObserver.__del__ of <__main__.AccountObserver object at 0x...>>
# Traceback (most recent call last):
# File 'Account_Observer.py', line 31, in __del__
# self.theaccount.unregister(self)
# File 'Account_Observer.py', line 17, in unregister
# self.observers.remove(observer)
# AttributeError: 'Account' object has no attribute 'observers'
#
# nach a, a_ob und gc.collect
#
# Account: del called for <__main__.Account object at 0x...>
# Account no longer in use
#
# nach a2, a_ob2 und gc.collect
Objekt-Repräsentation und Attribut-Bindung¶
# Objekt-Repräsentation und Attribut-Bindung
from garbage_collection import Account
a = Account('Guido', 1100.0)
print(a.__dict__) # Dictionary der Instanz-Attribute
# {'name': 'Guido', 'balance': 1100.0, 'observers': set()}
a.number = 123456 # Attribut 'number' wird zu a.__dict__ hinzugefügt
print(a.__class__) # die Klasse des Objekts
# <class '__main__.Account'>
print(Account.__dict__.keys()) # im Dictionary der Klasse stehen deren Methoden
# dict_keys(['__module__', '__init__', '__del__', 'register', 'unregister',
# 'notify', 'withdraw', '__dict__', '__weakref__', '__doc__'])
print(Account.__bases__) # die Basis-Klassen der Klasse
# (<class 'object'>,)
print(Account.__mro__) # MRO - method resolution order (gilt für alle
# Attribute, nicht nur Methoden)
# (<class '__main__.Account'>, <class 'object'>)
# der Attribut-Zugriff beim Lesen, Schreiben und Löschen erfolgt nach einem
# etwas komplexeren Algorithmus, der Deskriptoren beachtet:
#
# https://www-user.tu-chemnitz.de/~hot/PYTHON/#attr_access
# hier werden die speziellen Methoden __getattr__() und __setattr__()
# implementiert
class Circle(object):
def __init__(self, radius):
self.radius = radius
def __getattr__(self, name):
if name == 'area':
return math.pi * self.radius ** 2
elif name == 'perimeter':
return 2 * math.pi * self.radius
else:
return super().__getattr__(name)
def __setattr__(self, name, value):
if name in ('area', 'perimeter'):
raise TypeError('%s is readonly' % name)
object.__setattr__(self, name, value)
__slots__¶
# __slots__
# - Festlegung der Attribute der Klasse
# - kein Sicherheits-Feature, sondern Verbesserung der Effizienz
# - Attribute werden über Deskriptoren realisiert
# - standardmäßig wird auf __dict__ verzichtet
# - man kann aber den Slot __dict__ benennen, dann hat man wieder ein __dict__
class Account(object):
__slots__ = 'name', 'balance'
a = Account()
a.name = 'Dave'
try:
a.age = 40
except Exception as e:
print(repr(e))
# AttributeError("'Account' object has no attribute 'age'",)
print(type(Account.balance))
# <class 'member_descriptor'>
class Account(object):
__slots__ = 'name', 'balance', '__dict__'
a = Account()
a.age = 40
print(vars(a))
Operator-Überladung¶
# Operator-Überladung
# Hinweis: Python hat den eingebauten Datentyp Complex, die folgende
# Implementierung ist daher in der Praxis unnötig
#
# type(2+3j)
# <class 'complex'>
class Complex(object):
def __init__(self, real, imag = 0):
self.real = float(real)
self.imag = float(imag)
def __repr__(self):
return 'Complex(%s,%s)' % (self.real, self.imag)
def __str__(self):
return '(%g+%gj)' % (self.real, self.imag)
# self + other
def __add__(self, other):
print('_add__', self, other)
return Complex(self.real + other.real, self.imag + other.imag)
def __radd__(self,other):
print('_radd__', self, other)
return Complex(other.real + self.real, other.imag + self.imag)
# self - other
def __sub__(self, other):
print('_sub__', self, other)
return Complex(self.real - other.real, self.imag - other.imag)
def __rsub__(self, other):
print('_rsub', self, other)
return Complex(other.real - self.real, other.imag - self.img)
c = Complex(2,3)
print(c + 4.0)
print(4.0 + c)
print(repr(c + 4.0))
print(repr(4.0 + c))
# ('_add__', Complex(2.0,3.0), 4.0)
# (6+3j)
# ('_radd__', Complex(2.0,3.0), 4.0)
# (6+3j)
# ('_add__', Complex(2.0,3.0), 4.0)
# Complex(6.0,3.0)
# ('_radd__', Complex(2.0,3.0), 4.0)
# Complex(6.0,3.0)
# Complex.__add__() funktioniert, da die eingebauten Zahlen die Attribute real
# und imag haben
#
# mit __radd__() und __rsub__() funktionieren auch Ausdrücke wie
# 4.0 + c ==> c.__radd__(4.0)
# 4.0 - c ==> c.__rsub__(4.0)
#
# wenn 4.0 + c scheitert, wird __radd__() versucht, bevor Python einen
# TypeError wirft
# ohne __radd__() sähe es so aus:
4.0 + c
# Traceback (most recent call last):
# File '<stdin>', line 2, in <module>
# TypeError: unsupported operand type(s) for +: 'float' and 'Complex'
Typen und Tests auf Klassenzugehörigkeit¶
# Typen und Tests auf Klassenzugehörigkeit
class A(object): pass
class B(A): pass
class C(object): pass
a = A() # Instanz von A
b = B() # Instanz von B
c = C() # Instanz von C
type(a) # <class '__main__.A'>
isinstance(a, A) # True
isinstance(b, A) # True (B ist von A abgeleitet)
isinstance(b, C) # False (C ist nicht von A abgeleitet)
issubclass(B, A) # True
issubclass(C, A) # False
class Foo(object):
def spam(self, a, b):
pass
# FooProxy und Foo sind sind funktionell identisch, aber nicht voneinander
# abgeleitet
class FooProxy(object):
def __init__(self, f):
self.f = f
def spam(self, a, b):
return self.f.spam(a, b)
f = Foo()
g = FooProxy(f)
isinstance(g, Foo) # False
# Gruppierung funktionell identischer Klassen (Klassen mit demselben Interface)
#
# allg. besser ist die Gruppierung über Abstrakte Basisklassen
class IClass(object):
def __init__(self):
self.implementors = set()
def register(self,C):
self.implementors.add(C)
def __instancecheck__(self, x):
# von isinstance gerufen
return self.__subclasscheck__(type(x))
def __subclasscheck__(self, sub):
# von issubclass gerufen
return any(c in self.implementors for c in sub.__mro__)
IFoo = IClass()
IFoo.register(Foo)
IFoo.register(FooProxy)
f = Foo()
g = FooProxy(f)
isinstance(f, IFoo) # True
isinstance(g, IFoo) # True
issubclass(FooProxy, IFoo) # True
Abstrakte Basisklassen (ABC, Abstract Base Classes)¶
# Abstrakte Basisklassen (ABC, Abstract Base Classes)
from abc import ABCMeta, abstractmethod, abstractproperty
class Foo(metaclass = ABCMeta):
@abstractmethod
def spam(self, a, b):
print('spam')
@abstractproperty
def name(self):
pass
# ABCs kann man nicht instanziieren:
#
# f = Foo()
#
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: Can't instantiate abstract class Foo with abstract methods name, spam
# zulässig ist der Ruf abstrakter Methoden aus einer Subklasse
class Bar(Foo):
def spam(self, a, b):
super().spam(a, b)
def name(self):
pass
b = Bar()
b.spam(1, 2)
# Klassen können sich bei einer ABC registrieren
class Grok:
def spam(self, a, b):
print('Grok.spam')
Foo.register(Grok)
print(isinstance(Grok(), Foo)) # True
print(issubclass(Grok, Foo)) # True
Metaklassen¶
# Metaklassen
import collections
# Kreierung der Klasse Foo:
#
# class Foo():
# ...
class_name = 'Foo' # Name der Klasse
class_parents = (object,) # Tupel der Basisklassen
class_body = ''' # Klassenkörper (class body)
def __init__(self, x):
self.x = x
def blah(self):
print('Hello World')
'''
class_dict = {} # Klassen-Dictionary
# den Körper im lokalen Dictionary ausführen
exec(class_body, globals(), class_dict)
# das Klassen-Objekt Foo über die Metaklasse "type" schaffen
Foo = type(class_name, class_parents, class_dict)
# die Metaklasse könnte man auch explizit angeben
class Foo(metaclass = type):
pass
# https://docs.python.org/3/reference/datamodel.html?highlight=hook#metaclass-example
class OrderedClass(type):
@classmethod
def __prepare__(metacls, name, bases, **kwds):
print('\nOrderedClass __prepare__():\n%s\n' % repr((metacls, name, bases, kwds)))
return collections.OrderedDict()
def __new__(cls, name, bases, namespace, **kwds):
print('OrderedClass __new__():')
print(' %r' % cls)
print(' %r' % name)
print(' %s' % repr(bases))
print(' %r' % namespace)
print(' %r' % kwds)
print(' type(namespace) = %s\n' % type(namespace))
# type() erwartet für den Namespace ein dict() und kein OrderedDict()
#result = type.__new__(cls, name, bases, dict(namespace))
result = super().__new__(cls, name, bases, dict(namespace))
# im Attribut "members" merken wir uns als Tupel die Keys des
# OrderedDicts "namespace" in der richtigen Reihenfolge
result.members = tuple(namespace)
return result
class A(metaclass = OrderedClass):
def one(self): pass
def two(self): pass
def three(self): pass
def four(self): pass
print(A.members)
# ('__module__', 'one', 'two', 'three', 'four')
class D(A):
def two(): pass
def five(): pass
print(D.members)
# ('__module__', '__qualname__', 'two', 'five')
Klassen-Dekoratoren¶
# Klassen-Dekoratoren
# anders als Metaklassen wirken sie nur auf die dekorierte Klasse und nicht auf
# abgeleitete Klassen
registry = {}
def register(cls):
registry[cls.__clsid__] = cls
return cls
@register
class Foo(object):
__clsid__ = '123-456'
def bar(self):
pass
print(registry)
# ohne Dekorator:
class Foo(object):
__clsid__ = '123-456'
def bar(self):
pass
register(Foo) # die Klasse registrieren
print(registry) # die Registry ausgeben