Klasse dynamisch subklassieren...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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()
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

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.
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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...
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

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()
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

/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 :o
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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 :o
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... :-D
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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 :o
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... :-D
Danke für den Tip, ich kenne SQLAlchemy. Ich brauche für mein Projekt aber einen völlig anderen Denkansatz :wink:
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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 :o
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()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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.
Das Leben ist wie ein Tennisball.
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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!
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?
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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.
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.
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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! :?
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.
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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! :o
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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! :o
Zuletzt geändert von Red Rooster am Freitag 9. November 2012, 17:55, insgesamt 1-mal geändert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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.
Das Leben ist wie ein Tennisball.
Red Rooster
User
Beiträge: 12
Registriert: Donnerstag 8. November 2012, 13:30

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.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ach, das ist sicher schon jedem mal mit __getattr__ passiert. Irgendwann denkt man halt dran ;-)
Das Leben ist wie ein Tennisball.
Antworten