Seite 1 von 1
Klasse dynamisch subklassieren...
Verfasst: Donnerstag 8. November 2012, 13:42
von Red Rooster
Die Situation: Ich habe eine Klasse, die beim __init__ feststellt, welche andere Klasse subklassiert werden soll. So wie ich es versucht habe, geht es natürlich nicht. Aber wie dann?
Code: Alles auswählen
class Database(object):
def __init__(self, engine):
if engine == 'postgresql':
self = MySQL()
if engine == 'mysql':
self = PostgreSQL()
class PostgreSQL(object):
def select(self):
print 'select for pg'
class MySQL(object):
def select(self):
print 'select for mysql'
db = Database(engine='mysql')
db.select()
Re: Klasse dynamisch subklassieren...
Verfasst: Donnerstag 8. November 2012, 14:03
von cofi
Willkommen im Forum!
Vergiss die Vererbung und benutze eine Factory Funktion:
Code: Alles auswählen
def create_db_connection(self, engine):
return {'postgresql': PostgreSQL,
'mysql': MySQL}[engine]()
Du kannst natuerlich auch bei deiner `if`-Kaskade bleiben.
Re: Klasse dynamisch subklassieren...
Verfasst: Donnerstag 8. November 2012, 14:13
von Red Rooster
cofi hat geschrieben:Willkommen im Forum!
Vergiss die Vererbung und benutze eine Factory Funktion:
Code: Alles auswählen
def create_db_connection(self, engine):
return {'postgresql': PostgreSQL,
'mysql': MySQL}[engine]()
Du kannst natuerlich auch bei deiner `if`-Kaskade bleiben.
Darauf bin ich auch schon gekommen. Allerdings muß die Klasse "Database" an anderer Stelle wieder abgeleitet werden, deshalb geht das mit der Factory-Funktion leider nicht... Ich kann ja schlecht versuchen, eine Klasse von einer Funktion abzuleiten...
Re: Klasse dynamisch subklassieren...
Verfasst: Donnerstag 8. November 2012, 14:32
von /me
Red Rooster hat geschrieben:Aber wie dann?
So vielleicht:
Code: Alles auswählen
class Database(object):
def __new__(cls, engine):
if engine == 'mysql':
cls = MySQL
elif engine == 'pg':
cls = PostgreSQL
else:
raise Exception('Unsupported engine')
return object.__new__(cls)
class PostgreSQL(Database):
def select(self):
print('select for pg')
class MySQL(Database):
def select(self):
print('select for mysql')
db = Database(engine='mysql')
db.select()
db = Database(engine='pg')
db.select()
db = Database(engine='oracle')
db.select()
Re: Klasse dynamisch subklassieren...
Verfasst: Donnerstag 8. November 2012, 14:37
von Red Rooster
/me hat geschrieben:Red Rooster hat geschrieben:Aber wie dann?
So vielleicht:
Code: Alles auswählen
class Database(object):
def __new__(cls, engine):
if engine == 'mysql':
cls = MySQL
elif engine == 'pg':
cls = PostgreSQL
else:
raise Exception('Unsupported engine')
return object.__new__(cls)
class PostgreSQL(Database):
def select(self):
print('select for pg')
class MySQL(Database):
def select(self):
print('select for mysql')
db = Database(engine='mysql')
db.select()
db = Database(engine='pg')
db.select()
db = Database(engine='oracle')
db.select()
Danke für die prompte Antwort! Ja, ich glaube das ist genau was ich gesucht habe. Kann gar nicht glauben, daß es so einfach ist

Re: Klasse dynamisch subklassieren...
Verfasst: Donnerstag 8. November 2012, 18:29
von Hyperion
Red Rooster hat geschrieben:
Danke für die prompte Antwort! Ja, ich glaube das ist genau was ich gesucht habe. Kann gar nicht glauben, daß es so einfach ist

Ohne den genauen Hintergrund zu kennen würde ich ja sagen, Du suchst einen
ORM. Da böte sich im Python Umfeld
SQLAlchemy an - es sein denn, Du willst unbedingt Deinen eigenen schreiben...

Re: Klasse dynamisch subklassieren...
Verfasst: Donnerstag 8. November 2012, 19:14
von Red Rooster
Hyperion hat geschrieben:Red Rooster hat geschrieben:
Danke für die prompte Antwort! Ja, ich glaube das ist genau was ich gesucht habe. Kann gar nicht glauben, daß es so einfach ist

Ohne den genauen Hintergrund zu kennen würde ich ja sagen, Du suchst einen
ORM. Da böte sich im Python Umfeld
SQLAlchemy an - es sein denn, Du willst unbedingt Deinen eigenen schreiben...

Danke für den Tip, ich kenne SQLAlchemy. Ich brauche für mein Projekt aber einen völlig anderen Denkansatz

Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 12:43
von Red Rooster
Red Rooster hat geschrieben:/me hat geschrieben:Red Rooster hat geschrieben:Aber wie dann?
So vielleicht:
Code: Alles auswählen
class Database(object):
def __new__(cls, engine):
if engine == 'mysql':
cls = MySQL
elif engine == 'pg':
cls = PostgreSQL
else:
raise Exception('Unsupported engine')
return object.__new__(cls)
class PostgreSQL(Database):
def select(self):
print('select for pg')
class MySQL(Database):
def select(self):
print('select for mysql')
db = Database(engine='mysql')
db.select()
db = Database(engine='pg')
db.select()
db = Database(engine='oracle')
db.select()
Danke für die prompte Antwort! Ja, ich glaube das ist genau was ich gesucht habe. Kann gar nicht glauben, daß es so einfach ist

Verdammt. Leider scheint es doch nicht so einfach zu sein! Wenn ich jetzt die Klasse "Database" nehme und ableite, dann wird alles in der abgeleiteten Klasse überschrieben (und außerdem wird __init__ nicht mehr ausgeführt). Es sollte aber umgekehrt sein, daß die ableitende Klasse den Inhalt der Elternklasse überschreibt (wie es eigentlich üblich ist).
Code: Alles auswählen
class Database(object):
def __new__(cls, engine):
if engine == 'mysql':
cls = MySQL
elif engine == 'pg':
cls = PostgreSQL
else:
raise Exception('Unsupported engine')
return object.__new__(cls)
class PostgreSQL(Database):
def select(self):
print('select for pg')
class MySQL(Database):
def select(self):
print('select for mysql')
# ----------------------------------------------------------
class my_db(Database):
def __init__(self, engine):
print 'engine'
def select(self):
print 'select from my_db'
db = my_db('pg')
db.select()
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 13:26
von EyDu
Du musst in der __init__-Methode von my_db auch die __init__ von Database aufrufen.
Edit: In diesem Fall liegt es natürlich daran, dass du für die Engine "pg" eine Instanz von PostgreSQL erstellst, da wird natürlich die init von my_db aufgerufen. Das passiert halt, wenn man ein schlechtes Design wählt

Spezialisierungen sollten immer in der abgeleitetend Klasse stehen, die Elternklasse sollte gar nicht wissen, welche verschiedenen Ableitungen es gibt. Mit einer abstrakten Methode zur Initialisierung, welche durch die Kinder bereitgestellt und in der __init__ von Database ausgeführt wird, sparst du dir eine ganze Menge Ärger.
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 13:36
von Red Rooster
EyDu hat geschrieben:Du musst in der __init__-Methode von my_db auch die __init__ von Database aufrufen.
Edit: In diesem Fall liegt es natürlich daran, dass du für die Engine "pg" eine Instanz von PostgreSQL erstellst, da wird natürlich die init von my_db aufgerufen.
Die __init__ von my_db wird nicht aufgerufen, weil die Klasse my_db von der Klasse PostgreSQL überschrieben wird!
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 13:52
von BlackJack
@Red Rooster: Du versuchst da etwas was IMHO mit einer hierarchischen Klassenhierarchie so nicht geht. Was Du willst ist ja nicht von `Database` abzuleiten, sondern von mehreren Unterklassen davon quasi gleichzeitig abzuleiten. Das ist nicht vorgesehen.
Du solltest besser aus Deiner `MyDB` einen Proxy zu einer DB-Klasse machen. Dann ist es zum Beispiel auch ganz einfach die `select()` von der konkreten Datanbankklasse aus der überschriebenen `select()` heraus aufzurufen. Wie hättest Du Dir das bei Deinem Ansatz denn vorgestellt?
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 14:14
von Red Rooster
BlackJack hat geschrieben:@Red Rooster: Du versuchst da etwas was IMHO mit einer hierarchischen Klassenhierarchie so nicht geht. Was Du willst ist ja nicht von `Database` abzuleiten, sondern von mehreren Unterklassen davon quasi gleichzeitig abzuleiten. Das ist nicht vorgesehen.
Du solltest besser aus Deiner `MyDB` einen Proxy zu einer DB-Klasse machen. Dann ist es zum Beispiel auch ganz einfach die `select()` von der konkreten Datanbankklasse aus der überschriebenen `select()` heraus aufzurufen. Wie hättest Du Dir das bei Deinem Ansatz denn vorgestellt?
Die Hauptklasse "Database" soll sich zunächst selbst überschreiben (mit einer der Adapter-Klassen MySQL und PostgreSQL). Das funktioniert ja auch mit dem bestehenden Konstrukt. Ich müßte aber die Klasse "Database" einfach wie eine ganz normale Klasse ableiten können, ohne daß an dieser Stelle meine Unterklasse (my_db) überschrieben wird.
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 14:26
von BlackJack
@Red Rooster: Mir ist schon klar was Du willst, das ist aber nicht so einfach möglich, weil es eben der vorgesehenen Klassenhierarchie zuwider läuft.
Den Begriff „überschreiben” solltest Du hier vielleicht nicht verwenden, weil da nichts dergleichen passiert. Du rufst den Konstruktor von `my_db` auf, was der geerbte von `Database` ist. Und dort erstellst und gibst Du ein Exemplar der beiden anderen Unterklassen, abhängig vom Argument zurück. Von `my_db` wird niemals ein Exemplar erstellt.
Und ich sage es noch einmal: Lass diese Magie bleiben und schreibe einen Proxy statt das über Vererbung lösen zu wollen. Das ist eine saubere, einfache Lösung von der man auch die Klassenhierarchie zeichnen kann ohne Probleme zu bekommen.
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 16:04
von Red Rooster
BlackJack hat geschrieben:@Red Rooster: Mir ist schon klar was Du willst, das ist aber nicht so einfach möglich, weil es eben der vorgesehenen Klassenhierarchie zuwider läuft.
Den Begriff „überschreiben” solltest Du hier vielleicht nicht verwenden, weil da nichts dergleichen passiert. Du rufst den Konstruktor von `my_db` auf, was der geerbte von `Database` ist. Und dort erstellst und gibst Du ein Exemplar der beiden anderen Unterklassen, abhängig vom Argument zurück. Von `my_db` wird niemals ein Exemplar erstellt.
Und ich sage es noch einmal: Lass diese Magie bleiben und schreibe einen Proxy statt das über Vererbung lösen zu wollen. Das ist eine saubere, einfache Lösung von der man auch die Klassenhierarchie zeichnen kann ohne Probleme zu bekommen.
Ich kann keinen Proxy aus der my_db machen, weil sie in einer bestehenden Applikation an zig Stellen verwendet wird. BlackMagic ist hier äußerst angebracht, weil alle anderen Lösungen einen absolut unrealistischen Aufwand erfordern würden. Wenn das Problem nicht äußerst kompliziert wäre, dann würde ich hier gar nicht posten!

Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 16:25
von BlackJack
@Red Rooster: Ich sehe nicht warum Du keinen Proxy daraus machen können solltest. Das Entwurfsmuster zeichnet sich ja dadurch aus, dass es nach aussen wie der Datentyp verhält, für den es als Stellvertreter und Vermittler steht. Gerade bei dynamischer Typisierung geht so etwas doch sehr problemlos durch. Das hier ist deutlich weniger magisch als was Du da versuchst:
Code: Alles auswählen
class MyDB(object):
def __init__(self, engine):
self.db = Database(engine)
def __getattr__(self, name):
return getattr(self.db, name)
def select(self):
print 'select for MyDB, backed by:',
self.db.select()
Zusätzlich könnte man `Database` hier auch durch eine Fabrikfunktion ersetzen um das `__new__()` los zu werden.
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 16:53
von Red Rooster
BlackJack hat geschrieben:@Red Rooster: Ich sehe nicht warum Du keinen Proxy daraus machen können solltest. Das Entwurfsmuster zeichnet sich ja dadurch aus, dass es nach aussen wie der Datentyp verhält, für den es als Stellvertreter und Vermittler steht. Gerade bei dynamischer Typisierung geht so etwas doch sehr problemlos durch. Das hier ist deutlich weniger magisch als was Du da versuchst:
Code: Alles auswählen
class MyDB(object):
def __init__(self, engine):
self.db = Database(engine)
def __getattr__(self, name):
return getattr(self.db, name)
def select(self):
print 'select for MyDB, backed by:',
self.db.select()
Zusätzlich könnte man `Database` hier auch durch eine Fabrikfunktion ersetzen um das `__new__()` los zu werden.
Danke! Jetzt sehe ich, was Du meinst. Dann sollte ich wohl gleich aus Database eine Proxy-Klasse machen, das __new__ rausschmeißen und dann kann ich auch MyDB wieder ohne Problem von Database ableiten!
Ich hatte gar nicht gewußt, daß man __getattr__ auf eine beliebige Klasse umleiten kann!

Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 17:35
von Red Rooster
Red Rooster hat geschrieben:BlackJack hat geschrieben:@Red Rooster: Ich sehe nicht warum Du keinen Proxy daraus machen können solltest. Das Entwurfsmuster zeichnet sich ja dadurch aus, dass es nach aussen wie der Datentyp verhält, für den es als Stellvertreter und Vermittler steht. Gerade bei dynamischer Typisierung geht so etwas doch sehr problemlos durch. Das hier ist deutlich weniger magisch als was Du da versuchst:
Code: Alles auswählen
class MyDB(object):
def __init__(self, engine):
self.db = Database(engine)
def __getattr__(self, name):
return getattr(self.db, name)
def select(self):
print 'select for MyDB, backed by:',
self.db.select()
Zusätzlich könnte man `Database` hier auch durch eine Fabrikfunktion ersetzen um das `__new__()` los zu werden.
Danke! Jetzt sehe ich, was Du meinst. Dann sollte ich wohl gleich aus Database eine Proxy-Klasse machen, das __new__ rausschmeißen und dann kann ich auch MyDB wieder ohne Problem von Database ableiten!
Ich hatte gar nicht gewußt, daß man __getattr__ auf eine beliebige Klasse umleiten kann!

Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 17:46
von EyDu
Sagt doch die Fehlermeldung aus: self.db existiert in Database.select nicht, also wird __getattr__ aufgerufen. Das versucht auf dem Objekt self.db, welches es natürlich nicht gibt, etwas zu machen. Also wird __getattr__ aufgerufen um self.db zu finden. Den Rest kannst du dir ja vorstellen.
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 17:56
von Red Rooster
EyDu hat geschrieben:Sagt doch die Fehlermeldung aus: self.db existiert in Database.select nicht, also wird __getattr__ aufgerufen. Das versucht auf dem Objekt self.db, welches es natürlich nicht gibt, etwas zu machen. Also wird __getattr__ aufgerufen um self.db zu finden. Den Rest kannst du dir ja vorstellen.
Danke! Jetzt seh' ich den Wald vor lauter Bäumen nicht mehr... Ich mach' Feierabend für heute.
Re: Klasse dynamisch subklassieren...
Verfasst: Freitag 9. November 2012, 17:57
von EyDu
Ach, das ist sicher schon jedem mal mit __getattr__ passiert. Irgendwann denkt man halt dran
