was macht sessionmaker()

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.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Die Doku zum Thema habe ich gelesen.
[url]file:///usr/share/doc/python-sqlalchemy-doc/html/orm/session.html#sqlalchemy.orm.session.sessionmaker[/url]

Scheinbar habe ich jedoch ein Verständnisproblem, da ich aus der C++-Ecke komme. ;)
Konkret geht es um diese beiden Zeilen:

Code: Alles auswählen

Session = sessionmaker()
sess = Session(bind=connection)
Zeile 2 erzeugt ein Objekt/Instanz der Klasse "Session". Ok?

Aber was macht Zeile 1? Wenn ich die Doku richtig verstehe, gibt sessionmaker() eine Klasse zurück.
Aber das geht doch gar nicht. Ist in Zeile 1 "Session" eine Klasse (also nur der Bauplan eines
Objekts) oder ein Objekt (also die Instanz im Speicher)?

Die Klasse "Session" sollte doch bereits mit der passenden import-Anweisung bekannt sein. Daher
verstehe ich die Notwendigkeit der ersten Zeile nicht.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@MoonKid: in Python sind Klassen genauso Objekte wie Funktionen und Zahlen auch. Warum sollte also eine Funktion nicht eine Klasse zurückgeben können?
In diesem Fall wird aber keine neue Klasse erzeugt, sondern nur eine Instanz einer Factory-Klasse, die Session-Instanzen erzeugen kann. Damit spart man sich, ständig die selben Parameter beim Erzeugen einer Session angeben zu müssen.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Vielleicht liegt das an meiner C++-Denke, aber ich kann dem immer noch nicth folgen.
Was ein Factory-Pattern ist, weiß ich. Dafür brauchte ich in C++ keine Instanz, sondern habe direkt eine öffentliche (public) Klassenmethode der Klasse aufgerufen, die mir dann eine Instanz von sich selbst zurückgab. Dafür musste ich die Klasse aber nicht "erzeugen", sondern einfach per #include-Anweisung den passenden Header holen.

Mit der Aussage, in Python seien Klassen, das gleiche wie Objekte, bin ich endgültig verwirrt. ;)

Hab ein bisschen experimentiert. Einen Unterschied scheint es ja zu geben.

Code: Alles auswählen

>>> x = sessionmaker()
>>> x
sessionmaker(class_='Session'bind=None, expire_on_commit=True, autoflush=True, autocommit=False)
>>> s = x()
>>> s
<sqlalchemy.orm.session.Session object at 0xb68edaac>
Das sieht auch "nett" aus.

Code: Alles auswählen

>>> a = sessionmaker()()
>>> a
<sqlalchemy.orm.session.Session object at 0xb66768ec>
Zuletzt geändert von MoonKid am Sonntag 18. Januar 2015, 16:32, insgesamt 1-mal geändert.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Zusätzlich zu dem, was Sirius3 geschrieben hat:

Code: Alles auswählen

>>> class Foo:
...     pass
...
>>> class Bar:
...     pass
...
>>> def baz():
...     return 7
...
>>> print(Foo())
<__main__.Foo object at 0x7fe3edac>
>>> print(Bar())
<__main__.Bar object at 0x7fe3edec>
>>> print(baz())
7
>>> def some_func(make_thing):
...     return make_thing()
...
>>> print(some_func(Foo))
<__main__.Foo object at 0x7fe3edec>
>>> print(some_func(Bar))
<__main__.Bar object at 0x7fe3edec>
>>> print(some_func(baz))
7
In specifications, Murphy's Law supersedes Ohm's.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Ui, das verwirrt noch mehr. "Foo" und "Bar" haben die gleiche Adresse? Wobei mir immer noch völlig unklar ist, warum eine Klasse überhaupt eine Adresse benötigt.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@MoonKid: Das Factory-Pattern in C++ sagt nur, dass ich eine Instanz einer Klasse nicht direkt erzeuge, sondern eine Funktion aufrufe, die die Instanz erzeugt. Ob das jetzt eine Funktion, eine statische Methode oder eine Methode einer anderen Klasse ist völlig egal. In Java sind das ganz gerne immer eigene Klassen. In Python sieht die Sache etwas anders aus, weil es syntaktisch keinen Unterschied zwischen einem Funktionsaufruf und einer Klassenerzeugung gibt (es gibt kein new Keyword). Wenn Du begriffen hast, dass Funktionen Objekte sind, dann ist der Schritt, dass Klassen auch Objekte sind, nicht mehr weit.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Moonkid: Die Adressen, die da angezeigt werden, sind nicht die der Klassen, sondern die von Objekten der Klassen. Außerdem hat uns hier der Garbage Collector einen Streich gespielt: zwei Objekte leben nie gleichzeitig an derselben Adresse.

Ich wollte auch auf etwas anderes hinaus. In Python ist alles ein Objekt. Auch Klassen und Funktionen, selbst Module. Deshalb braucht man das Factory Pattern in Python nicht. Statt dessen kann man einfach ein aufrufbares Objekt an eine Funktion übergeben die dieses dann verwendet. Das kann eine Funktion, eine Klasse oder irgendein anderes Objekt sein, sofern es die spezielle __call__() Methode implementiert:

Code: Alles auswählen

>>> class SomeCallable:
...     def __init__(self, num):
...         self.num = num
...     def __call__(self):
...         return self.num
...
>>> sc = SomeCallable(123)
>>> print(sc())
123
>>> print(some_func(sc))
123
In specifications, Murphy's Law supersedes Ohm's.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Mhm... Ok, ich nehm das jetzt einfach mal so hin. Technisch ist es mir auch klar, da ein Interpreter natürlich anders arbeitet als eine Compiler-Linker-Kombi.

Aber warum gibt es dann sessionmaker() ? Wozu der Käse?
Warum geht das nicht?

Code: Alles auswählen

from sqlalchemy.orm import Session

s = Session()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

MoonKid hat geschrieben:Mhm... Ok, ich nehm das jetzt einfach mal so hin. Technisch ist es mir auch klar, da ein Interpreter natürlich anders arbeitet als eine Compiler-Linker-Kombi.
Du wirst es nicht glauben, aber auch Python hat einen Compiler ;-) Wie der Code läuft ist in diesem Fall egal, dass ist eine Frage der Sprache und wie sie definiert ist.
MoonKid hat geschrieben:Aber warum gibt es dann sessionmaker() ? Wozu der Käse?
Das ist kein Käse, das spart jede Menge unnütze Arbeit. Den Grund hat Sirius3 schon im ersten Beitrag genannt ;-)
Das Leben ist wie ein Tennisball.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@MoonKid: naja, falsch geraten ist auch total daneben. Diesmal hat es nicht mal etwas mit dynamischer vs. statische Typisierung zu tun, weil man ja ein ganz ähnliches Verhalten in C++ mit Factory-Functions erreichen könnte. Höhere Programmiersprachen haben manchmal sowas halt schon eingebaut, während man das Low-Level Programmiersprachen wie C++ erst noch beibringen muß.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

UiUi, mir raucht der Kopf. ;)

Ok, nochmal ein (leicht vereinfachtes) Praxisbeispiel von mir. Kann man das so schreiben? Oder ist es schlechter Stil?

Code: Alles auswählen

import sqlalchemy
from sqlalchemy import create_engine, Column, Numeric, Integer
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

# -- Database --
class MDatabase():
    __engine = create_engine('postgres://puser:db@localhost/testdb', echo=False)
    __session = sessionmaker(bind=__engine)()

    def __init__(self):
        super(MDatabase, self).__init__()

# -- Data Layer classes --
class PMData(declarative_base()):
    __tablename__ = 'PM'

    __oid = Column('oid', Integer, primary_key=True)
    weight = Column(Numeric(10, 2))

    def __init__(self, weight):
        self.weight = weight
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Sirius3 hat geschrieben:In diesem Fall wird aber keine neue Klasse erzeugt, sondern nur eine Instanz einer Factory-Klasse, die Session-Instanzen erzeugen kann. Damit spart man sich, ständig die selben Parameter beim Erzeugen einer Session angeben zu müssen.
Warum macht man das mit einer extra Funktion und nicht mit dem "Konstruktor" Session.__init__ ?
Hättet ihr evtl. mal ein Negativ-Beispiel? Wie würde die Verwendung von Session aussehen, wenn es die Funktion sessionmaker() nicht gäbe?
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@MoonKid: Die Klasse MDatabase ist unnötig und macht ja auch gar nichts. Dafür gibt es ja Sessions. declerative_base erzeugt diesmal wirklich eine Klasse, die man aber nur einmal für alle Tabellen-Klassen erzeugen sollte. __init__ sollte zumindest das Basis-Klassen-__init__ aufrufen und ist in diesem Fall total unnötig, weil man beim Erzeugen einer Instanz alle Spalten als Keyword-Argumente übergeben kann. Doppelte Unterstriche sollte man, wie überall, nicht benutzen.
BlackJack

@MoonKid: Neben dem bereits gesagten würde ich `declarative_base()` nicht dort aufrufen wo Du das tust. Wenn überhaupt macht das nur Sinn wenn man nur eine Tabelle hat. Bei mehreren würde man so für jede Tabelle eine eigene Base-Klasse erstellen, aber man möchte ja eigentlich das alle ORM-Klassen von der selben Base-Klasse erben. Also zumindest alle die zusammengehören.

Ich würde die `__init__` von der Datenklasse ganz weglassen. Du sparst Dir damit ja eigentlich nur das man `weight` als Schlüsselwordargument angeben muss. Falls Du die `__init__()` unbedingt haben möchtest, solltest Du `weight` nicht als Attribut setzen sondern über den Aufruf der `__init__()` von der Basisklasse initialisieren. Denn ansonsten wird das Objekt ja erst einmal mit `weight` als `None` initialisiert was dann durch die Zuweisung ersetzt wird. Spätestens wenn Du ``NOT NULL``-Spalten (ohne ``DEFAULT``) definierst geht das so ja auch gar nicht mehr.

Wenn Du `sessionmaker()` gar nicht benutzt, dann kannst Du es natürlich auch sein lassen und `Session` importieren und direkt benutzen. `sessionmaker()` hat halt den Vorteil eine `Session`-Fabrik zu erstellen die anders konfiguriert ist als der Standard und man kann dann überall dieses „callable” benutzen um `Session`-Exemplare zu erstellen. Und wenn man etwas an der Konfiguration ändern möchte, kann man das beim `sessionmaker()`-Aufruf machen und das wirkt sich dann an allen Programmstellen aus an denen die erzeugte `Session`-Fabrik verwendet wird, ohne das man das überall im Quelltext ändern muss.

Ich weiss nicht ob's schon gesagt wurde: Du kannst Klassen als Fabrikfunktionen ansehen die Exemplare vom Datentyp der Klasse erzeugen.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Sirius3 hat geschrieben:@MoonKid: Die Klasse MDatabase ist unnötig und macht ja auch gar nichts.
Die Klasse ist hier stark gekürzt. Sie macht also schon was. Desweiteren ist sie die logische Abstraktion der Datenbank und deren Vebindung. "Session" stellt nur die Abstraktion auf technischer Ebene dar. Ich muss die Funktionaltiät und Logik der Datenbank unabhängig von technischen Aspekten (z.B. die verwendete lib sqlalchemy) kapseln. Die Klasse hat durchaus ihre Daseinsberechtigung.
Anders gefragt: Wo würdest du die Instanz von Session verorten?

Grundsätzlich gilt, dass ich im globalen Namensraum keine Objekte haben möchte, außer Instanzen meiner eigenen Klassen, die diverse Internas kapseln.
Sirius3 hat geschrieben:declerative_base erzeugt diesmal wirklich eine Klasse, die man aber nur einmal für alle Tabellen-Klassen erzeugen sollte.
Verstehe. Wo sollte ich dann "Base" verorten. Hierbei fällt mir die Instanz von MDatabase ein. Wie gesagt, "Base" hat im globalen Namensraum nix zu suchen.
Sirius3 hat geschrieben: __init__ sollte zumindest das Basis-Klassen-__init__ aufrufen und ist in diesem Fall total unnötig, weil man beim Erzeugen einer Instanz alle Spalten als Keyword-Argumente übergeben kann.
Das mit init der Basisklasse versteh ich. Aber was meinst du mit Keyword-Argumenten?
Sirius3 hat geschrieben:Doppelte Unterstriche sollte man, wie überall, nicht benutzen.
Wie meinst du das? Mit doppelten Unterstrichen dekariere ich private member - soweit ich weiß. Damit fällt mir auch grad ein Fehler im Code auf. "weight" ist gar nicht privat, wie es eigentlich sein sollte. ;)
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

BlackJack hat geschrieben:`sessionmaker()` hat halt den Vorteil eine `Session`-Fabrik zu erstellen die anders konfiguriert ist als der Standard und man kann dann überall dieses „callable” benutzen um `Session`-Exemplare zu erstellen.
Warum sollte ich mehrere Instanzen von Session benötigen?
(btw: Der Satz ist zweideutig interpretierbar, da Python ja keinen Unterschied zwischen Klassen und Objekten macht. Schwierig!)
Also ich meine Objekte der Klasse Session.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

MoonKid hat geschrieben:Warum sollte ich mehrere Instanzen von Session benötigen?
Weil man dann Sessions mit verschiedenen Engines und verschiedenen Einstellung erzeugen kann. Manchmal hat man ja mehr als eine Datenbank oder eine Session soll sich anders verhalten.
Das Leben ist wie ein Tennisball.
BlackJack

@MoonKid: Neben dem was EyDu geschrieben hat, ist da ja auch die Frage wie lange eine Session existiert. Es ist nicht unübliche eine Session pro in sich geschlossener Aufgabe zu erzeugen. Was das konkret bedeutet hängt von der Anwendung und vom Entwurf des Programmierers ab. Bei Webanwendungen ist die Lebenszeit einer Session in der Regel auf einen Anfrage/Antwort-Zyklus beschränkt. Da werden also nacheinander neue Session-Objekte erzeugt. Und da mehr als eine Anfrage gleichzeitig kommen kann, sind auch parallel existierende Session-Objekte ein ganz normaler Fall.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@MoonKid: Du hast seltsame Ansichten, was was "global" sein sollte. Jeder Import legt ein Deinem Namensraum Namen von fremden Modulen ab, das als Forderung zu haben, ist also nicht einhaltbar. Nur weil die Variable in einem Klassennamensraum steht, der keine wirkliche Klasse ist, ist sie dennoch global. Was ich damit sagen will, alles in ein MDatabase macht nichts besser, sondern nur umständlicher für Dich. Wenn Du kennzeichnen willst, dass etwas nicht zur öffentlichen API gehört, kann man es mit einem Unterstrich kennzeichnen. Auf das Base-Beispiel angewendet: Auf Modulebene ein

Code: Alles auswählen

_Base = declarative_base()
ist die einzig richtige Vorgehensweise. Alle DB-Klassen erben dann von _Base.
Eine Klasse die wiederum SQLAlchemy kapselt scheint mir unnötig, weil SQLAlchemy ja schon eine Kapselung der Datenbankschnittstellen ist. Aber vielleicht kannst Du mich ja noch vom Gegenteil überzeugen.
Sessions sind nicht Thread-Safe, dass man für jeden Thread eine eigene Session braucht. Ich habe zum Beispiel für jede Transaktion eine eigene Session.
MoonKid
User
Beiträge: 105
Registriert: Mittwoch 10. Dezember 2014, 16:24

Sirius3 hat geschrieben:@MoonKid: Du hast seltsame Ansichten, was was "global" sein sollte.
Sie sind eher strikt. Fremde Module kann ich ja nicht beeinflussen. Soweit ich es beeinflussen kann, wir es aber gekapselt. Und ja sqlalchemy kapselt bereits die Schnittstelle zur Datenabank. Aber ich möchte sqlalchemy als Biblitothek selbst auch kapseln. Wenn ich später sqlalchemy austauschen will, muss ich das nur in der einen Datenbankklasse tun. Der Rest des Codes bleibt unberührt.
Antworten