Problem mit Modulen/Objekten

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.
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Hallo,

habe ein kleines Problem und komme nicht weiter und hoffe auf eure Hilfe.

Es ist keine Hausaufgabe. In C++ und C# würde ich ein Singleton in Verbindung mit static Variablen einsetzen. In Python weiß ich leider nicht wie ich vorgehen soll.

Als praktisches Beispiel habe ich folgendes Problem: ich habe 3 Objekte: App, GUI, Session. Die App initializiert GUI und dann Session. Aus Session soll ein MessageBox angezeigt werden, dass wiederum in GUI hinterlegt ist. Und das Prinzip erweitert sich auf weitere noch nicht existierende Objekte/Classen.

Eine mögliche Lösung habe ich gefunden unter: http://stackoverflow.com/questions/6864 ... -in-python

Ist das die Lösung für das Problem, oder gibt es da eine bessere Lösung in Bezug auf Python?

Vielen Dank!
BlackJack

@cheaze: Ich sehe hier nichts statisches‽ Wenn Du aus einem `Session`-Exemplar auf etwas aus dem `GUI`-Exemplar zugreifen willst, dann solltest Du das `GUI`-Exemplar dem `Session`-Exemplar ganz einfach bekannt machen. Zum Beispiel es beim erstellen als Argument übergeben.
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Danke BlackJack. Als Parameter oder Globale Variable, mag ich es leider überhaupt nicht.

Habe jetzt doch noch mal Google bemüht und siehe da http://stackoverflow.com/questions/4255 ... on-pattern

Wie ich zunächst sehe, geht leider kein Weg am Singleton vorbei.
BlackJack

@cheaze: Ähm, wenn Du globale Variablen nicht magst, was ja gut ist, dann kannst Du auch Singleton nicht mögen, denn das sind doch globale Variablen.

Und diese ganzen Singleton-Implementierungen die es für Python so gibt sind alle irgendwo unsinnig. Python hat schon ”natürliche” Singletons: Module. Und wenn man ein Singleton von einem eigenen Typ haben möchte, dann erstellt man halt einfach nur *ein* Objekt davon. Dazu braucht man keine `__new__()`-Implementierung und keine Metaklassenmagie. Man erstellt einfach kein weiteres Objekt von dem Typ. Sondern nur eines. Auch nicht drei. Oder vier. Sondern genau eins. So einfach kann das sein. :-)

Edit: Welches GUI-Rahmenwerk verwendest Du eigentlich?
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Ok, leuchten ein. Ich versuche ein Objekt zu erstellen auf dass ich aus einem beliebigen anderen Modul einfach zugreifen kann. Als einfaches Beispiel (mit GUI):

Code: Alles auswählen

# PyQt4_Gui.py
from PyQt4 import QtGui
class GuiApp( QtGui.QMainWindow ):
    def __init__( self ):
        super( GuiApp, self ).__init__()
    def MessageBox( self, msg ):
        return QtGui.QMessageBox.information(self, ...)

Code: Alles auswählen

# Session.py
from PyQt4_Gui import GuiApp
class Session( object ):
    def __init__( self ):
        ### Hier der Knicks
        GuiApp(???).MessageBox("Test")
        ### Auch so nicht
        GuiApp.MessageBox("Test")
BlackJack

@cheaze: Wenn das `Session`-Objekt der Controller ist, dann muss der doch das `GuiApp`-Objekt kennen, also als Argument bei `__init__()` übergeben bekommen. Was spricht dagegen? Oder ist `Session` das Model? Dann ist der Wunsch von dort aus etwas mit der GUI zu machen fragwürdig und das Objekt sollte die reinen Informationen an den Controller weiterleiten der dann die Anzeige in der GUI veranlasst.
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

@BlackJack: Ich habe mir wahrscheinlich zu viel von C# reingezogen :shock:
GUI und Session haben keinen Einfluß untereinander. Sogut wie überhaupt nichts. GUI läuft als reine Oberfläche; daraus werden per Ereignisse Daten aus der Session oder einer DB abgerufen. Die Session steuert nebenbei die DB. DB und Session sollen die GUI nicht steuern, jedoch zugriff haben um eine Message anzuzeigen oder ein Log ausgeben im Fenster. Einen Controller zw. DB/Session und GUI soll es nicht geben. Es werden nur Ereignis abgearbeitet, danach schlafen die Objekte, behalten jedoch einige Werte für den nächsten Abruf. Es soll z.B. nicht erneut eine Datenbank angelegt werden, wenn bereits geschehen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

cheaze hat geschrieben:GUI und Session haben keinen Einfluß untereinander. Sogut wie überhaupt nichts.
"So gut wie überhaupt nichts" ist eben nicht Nichts ;-) Deinen Schilderungen nach willst du sehr wohl einen Controller, auch wenn du ihn so nicht nennen möchtest. Wenn eine Session nicht für die GUI verantwortlich ist und die GUI nicht für die Sessions, dann bleibt doch nur ein Zwischenschritt. In deinem Fall muss der Controller doch auch gar nicht viel machen: Er nimmt Nachrichten von einer Session entgegen und leitet diese an die GUI weiter. So kennen sich GUI und Session nicht und beide können gemütlich ohne die Abhängigkeit des anderen arbeiten. Saubere Lösung mit wenig Code.
Das Leben ist wie ein Tennisball.
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Vielleicht ein wenig salopp ausgedrück :wink:

Genau diesen "Controller" versuche ich zu umgehen, indem ich direkt auf Objekte zugreifen kann. Die beste Lösung bis jetzt, ist ein Singleton.

Edit: Session arbeitet nicht im Hintergrund, daher bedarf es ebenfalls keinen Zwischenkontroller, so wie ich es sehe.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Dann spricht doch nichts gegen die erste Lösung von BlackJack. Übergib der Session die GUI:

Code: Alles auswählen

def main():
    gui = GUI()
    session = Session(gui)
    ...
Wenn du Nachrichten von der Session an die GUI schicken willst, dann muss die Session die GUI doch von irgendwo kennen. Egal ob nun direkt oder indirekt über einen Controller.
Das Leben ist wie ein Tennisball.
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Das muss dann aber in meinen Fall so sein:

Code: Alles auswählen

def main():
    gui = GUI()
    sess = Session()
    db = DbCnn()
    gui.setSession(sess)
    gui.setDb(db)
    sess.setGui(gui)
    sess.setDb(db)
    db.setGui(gui)
:?

Das muss auch anders gehen, da bin ich mir sicher... Ich werde morgen mit Singleton ausprobieren und melde mich dann wieder. Für heute erst mal Schluß.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@cheaze: Kannst Du mal die Abhängigkeiten für Dich auf einem Blatt aufmalen? So wie ich das sehe, ist dann alles von allem abhängig. Inklusive aller Probleme, die man damit hat. Dafür gibt es ja gerade die Model-Controller-View-Konzepte, um sich nicht in einem Spinnennetz zu verheddern. Da hilft es auch nicht, die Abhängigkeiten durch globale Variablen zu verstecken, ganz im Gegenteil.
Warum hat die GUI Zugriff auf die Datenbank und umgekehrt?
Wie willst Du Deine Session sinnvoll testen, wenn Du immer eine GUI-Instanz dafür brauchst?
Wenn Du die GUI in Session nur für Log-Ausgaben brauchst, dann erstelle doch eine passende Logging-Klasse. Dann kannst Du auch ganz einfach eine Test-Klasse schreiben, die prüft, ob die richtigen Logmeldungen kommen.

Das sähe dann so aus:

Code: Alles auswählen

def main():
    logger = Logger()
    db = DbCnn(logger)
    sess = Session(db, logger)
    gui = GUI(sess)
    logger.add_frontend(gui)
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Danke Sirius,

die Probleme kenne ich aus den Zeiten mit C++, das Gleiche im Prinzip: Anhändigkeiten lösen, Objekte klar definieren und Überschneidungen vermeiden. So in etwa. Die einfachste Lösung war hier ein Singleton.

In C# sieht die Sache komplett anders aus. Da hatte ich dieses Problem noch gar nicht :K

Ich blicke langsam nich mehr durch. Ein Singelton Versuch scheitert bei mit dem Error:

Code: Alles auswählen

class MainWindow( QtGui.QMainWindow ):
TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Das geht leider jetzt über meine Anforderungen hinaus :(

Wenn es eine brauchbare Lösung gibt, a'la Prinzip C#, werde ich es mir noch mal überlegen. Anderenfalls versuche ich lieber den Weg des "geringsten Widerstandes". Sorry Leute :mrgreen:

Danke dennoch an alle Beteiligten und Unbeteiligten!
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Falls Anfragen sein sollten, oder Jemand da, der sich damit ebenfalls befassen möchte, hier mein Code das nicht funktioniert. Jedenfall nicht wie ich es vorhatte:

main.py

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
from app import Application

def main():
	Application(sys.argv).run()

if __name__ == '__main__':
	main()
app.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

import sys

from gui     import MainWindow
from session import Session

from PyQt4 import Qt

class Application( Qt.QApplication ):
	
	def __init__( self, args ):
		
		Qt.QApplication.__init__( self, args )
		self.root = MainWindow()
		
		Session().TestGUI()
		
	def run( self ):
		self.root.show()
		sys.exit( self.exec_() )
gui.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

from session import Session

from PyQt4 import Qt, QtGui

class Singleton( type ):
	def __init__( cls, name, bases, dict ):
		super(Singleton, cls).__init__(name, bases, dict)
		cls.instance = None
		
	def __call__(cls, *args, **kw):
		if cls.instance is None:
			cls.instance = super(Singleton, cls).__call__(*args, **kw)
		return cls.instance

class MainWindow( QtGui.QMainWindow ):
	
	__metaclass__ = Singleton
	
	def __init__( self ):
		
		super( MainWindow, self ).__init__()
		b = QtGui.QPushButton( 'Test', self )
		#b.clicked.connect( Session.instance().TestGUI() )
		b.clicked.connect( Session().TestGUI() )
	
	def MessageBox( self, message ):
		return QtGui.QMessageBox.information( self, 'Info',
			message, QtGui.QMessageBox.Ok )
session.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

class Session( object ):
	
	def __init__( self ): pass
		
	def TestGUI( self ):
		
		from gui import MainWindow
		MainWindow().MessageBox( "Test Gui" )
Falls wer noch eine Idee hat, wie dieser Code korrigiert werden kann, gerne hier einstellen. Vielen Dank!
BlackJack

@cheaze: Singletons sind nicht die einfachste Lösung des Problems weil sie das Problem der Abhängigkeiten gar nicht lösen. Ob die ganzen Objekte sich kennen und voneinander abhängig sind, weil man sie am Anfang alle mit diesen `set*()`-Methoden bekannt macht, oder ob die sich kennen und voneinander abhängig sind weil sie alle global existieren und einfach so aufeinander zugreifen, ändert doch gar nichts. Es macht die Sache nur noch unflexibler, weil man nicht einmal mehr zum Testen Mock-Objekte setzen kann, und es macht es für den Leser undurchsichtiger weil die Abhängigkeiten nun über den gesamten Quelltext verstreut versteckt sind.

Das ist ein grundsätzliches OOP-Entwurfsproblem, das es in C++, C#, und Python in gleicher Weise gibt.

Was immer Du da beim Singleton versucht hast: Man muss für ein Singleton keinen speziellen magischen Code schreiben, sondern ganz einfach nur einmal ein Exemplar von dem entsprechenden Typ erstellen und irgendwo global verfügbar machen.

Eine brauchbare Lösung „a'la Prinzip C#” wäre es genug vom MVC-Entwurfsmuster umzusetzen um die Abhängigkeiten tatsächlich zu lockern, und nicht nur mit Singletons so zu kaschieren dass es nicht auf den ersten Blick auffällt, das die Objekte dadurch noch unflexibler voneinander abhängig sind.

Ein guter „Lackmustest” sind die von Sirius3 angesprochenen Tests. Schreib mal Unit-Tests für die Session und das Datenbankobjekt. Solange Du dafür ein Gui-Objekt benötigst, ist der Entwurf schlecht.

Zu den ganzen `set*()`-Methoden: Falls die tatsächlich nur ein Attribut an den übergebenen Wert binden, dann ist das „unpythonisch” und man würde einfach das Attribut direkt setzen.
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

@BlackJack: Da muss leider widersprechen. In C++ muss man immer noch Anhändigkeiten zwischen den Classen lösen bzw. im Blick behalten, dennoch können diese im selben Namensraum existiren. In C# kann ich dagegen Classen schreiben, die keinen Bezug zueinander haben und dennoch im selben Namensraum deklariert werden. Als Beispiel habe ich ein Logger, der als Singleton definiert ist und aus jedem beliebigen Objekt im selben Namensraum angesprochen werden kann, ohne dass dort eine Abhändigkeit hergestellt werden muss. Genau diese Prinzip fehlt mir hier leider.
BlackJack

@cheaze: Ich sehe da nicht wo Du mir widersprichst. Zwischen dem Objekt das den Logger verwendet und dem Logger besteht doch eindeutig und offensichtlich eine Abhängigkeit. Ohne den Logger funktioniert das Objekt nicht, es hängt von dessen Existenz ab. Und bei einem Singleton hängt es von einem globalen Wert ab. Das man diese Abhängigkeit nicht statisch deklariert, oder zumindest durch einen Aufruf zur Laufzeit vor der Verwendung herstellen muss, ist ja gerade was diese globalen Werte so unschön macht. Man sieht die Abhängigkeiten erst wenn man den ganzen Quelltext durchliest, weil die irgendwo in beliebigen Methoden versteckt sind.

In Python deklariert man ja *gar nichts* (also zumindest kann man das für das hier besprochene Problem mal so sagen) und man kann in einen Namensraum auch sowohl Klassen stecken die nichts miteinander zu tun haben, als auch Exemplare als Singletons erstellen die einfach so von anderen Objekten aus dem selben oder auch anderen Namensräumen benutzt werden. Ich weiss also nicht was Dir hier jetzt fehlt.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@cheaze: es geht hier nicht darum, ob der Compiler irgendwie Abhängigkeiten auflösen können muß, sondern darum, ob es rein logisch eine Abhängigkeit gibt. Wenn in Deiner Klasse der Logger benutzt wird, egal ob über Attribut, global Variable oder Singleton, gibt es eine Abhängigkeit. Im ersten Fall, als Attribut, wird diese Abhängigkeit bewußt gemacht, das heißt, der Quellcode-Leser sieht bei der Erzeugung, dass es eine Abhängigkeit gibt. Diese Abhängigkeit kann zum Beispiel beim Testen einfach durch ein Mockup ersetzt werden, was in den anderen Fällen nicht geht. Es können mehrere Logger-Instanzen existieren, falls man das irgendwann mal braucht, bzw. umgekehrt, falls es nötig wird, hast Du in den anderen beiden Fällen ein riesen Problem.
cheaze
User
Beiträge: 13
Registriert: Sonntag 6. April 2014, 21:02

Typischer Code eines Singletons:

Code: Alles auswählen

namespace Test
{
        public sealed class DebugLogger
	{
		private static DebugLogger instance = new DebugLogger();
		
		public static DebugLogger Instance {
			get {
				return instance;
			}
		}
        }
}
Hier stark vereinfacht. Ohne Vererbung, funktioniert jedoch im Prinzip gleich. Instance ist privat und ich habe keinen zusätzlichen Aufwand dieses Objekt noch wo Anders deklarieren zu müssen. Und wenn ich im Code sehe DebugLogger.instance()..., weiß ich schon wo ich suchen muss. Bzw. dass hier nur eine BlackBox vorliegt. Anhändigkeiten sehe ich hier keine. Ich kann diese Objekt beliebig testen, ohne einen bestehenden Code verändern zu müssen.
BlackJack

@cheaze: Natürlich siehst Du bei *einem* Objekt keine Abhängigkeiten, dazu braucht man schon zwei Objekte. :roll:

Und es geht nicht darum diesen Logger zu testen sondern ein Objekt das diesen Logger benutzt, und damit von ihm abhängig ist, ohne den Logger mitzutesten. Und wenn das so aussieht wie bei dem Code den Du gezeigt hast, kann man bei Dir keinen Teil des Programms isoliert ohne alles andere, inklusive GUI, testen weil alles voneinander abhängig ist.

Die Abhängigkeit die Du hier nicht siehst, die aber existiert, hat rein gar nichts mit einer konkreten Programmiersprache oder Syntax zu tun, sondern nur mit dem objektorientierten Paradigma und Objekten die zur Laufzeit existieren und voneinander abhängig sind.

Das Du weisst wo Du suchen musst *wenn* Du ``DebugLogger.instance()`` im Code siehst ist klar, aber woher weisst Du dass Du nach so etwas überhaupt suchen musst? Wenn man wissen will wovon ein Objekt abhängt, schaut man sich normalerweise an welche Attribute es besitzt und woran die gebunden werden. Das sieht man in der `__init__()` und der Dokumentation der Attribute. Und daran sollte man das auch sehen. Man sollte nicht alle Methoden durchschauen müssen um zu sehen welche globalen Werte ausser Konstanten noch so von diesem Objekt verwendet werden und damit versteckte Kommunikationskanäle zu anderen Objekten bilden von denen man auch nichts weiss ohne bei allen Klassen alle Methoden zu untersuchen.

Edit: Der Beispielcode würde in Python übrigens einfach so aussehen (in einem Modul `test`):

Code: Alles auswählen

class DebugLogger(object):
    pass


debug_logger = DebugLogger()
Das Singleton-Objekt wäre in diesem Fall das `test`-Modul, denn egal wie oft man das Importiert, man bekommt immer das selbe `module`-Exemplar. Und das hat halt auch immer dieses eine `DebugLogger`-Exemplar als Attribut.
Antworten