QLineEdit und anderes

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

Grüß Euch!

Ich habe mir mal erlaubt, Euch meinen Code zu posten, soweit ich ihn habe. Wenn Ihr mal drüber schauen und mir eventuell ein paar Hinweise geben könntet, wie ich was besser oder anders machen kann, würde ich mich sehr freuen.

Grundsätzlich beschäftige ich mich derzeit mit dem Problem, wie ich die Eingaben in die Eingabefelder abfangen und in einer Variablen abspeichern kann, die ich später, bei Betätigung des Bestätigungs-Buttons in die DB eintragen kann. Ich habe bislang nichts gefunden, dass mir die Umsetzung dieses Sachverhaltes verständlich darlegt. Irgendeine Idee?

Code: Alles auswählen

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

"""
Projekt:   MVC Test
Klassen:   Model, View, Control
Version:   1.0
Datum:     22.02.2017
Author:    the_real_noob

"""


import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLineEdit, QLabel, \
                            QPushButton
from PyQt5.Qt import QComboBox
import psycopg2


#==============================================================================#
# Model                                                                        #
#==============================================================================# 

class Model(object):
    
    def __init__(self):
        self.conn = None
        self.cache = []

   
    def db_connect(self, db_name):
        # Connect Database 
        try:
            print('Try to connect')
            self.conn = psycopg2.connect(db_name)
            print('Database connected')
        
        except psycopg2.OperationalError as e:
            print ("Unable to connect!")
            print (e.pgerror)

    
    def loadCities(self):
        if len(self.cache) == 0:
            cur = self.conn.cursor()
            cur.execute('SELECT * FROM city')
            a = cur.fetchall()
            #self.cache.append('')
            for b in a:
                # Nur Stadt anhängen, nicht id
                self.cache.append(b[1])          
            cur.close()
    
    def insertData(self, i):
        print('Insert Data', i)
          
        
    def getCache(self):   
        return self.cache
        
    def db_close(self):
        self.conn.close()
        
        
#==============================================================================#
# View                                                                         #
#==============================================================================#         
        

class View(QMainWindow):
    'View-Klasse - beinhaltet die Methoden: initMenubar, initTextLines,' 
    'initTextlabels, initButton und userInterface zum Zeichnen.'
    def __init__(self, eventManager):
        super().__init__()
        self.tmp = None
        self.eventManager = eventManager
        
    def initMenubar(self):

        menubar = self.menuBar()
        file = menubar.addMenu('File')
        
        #file.addAction(self.newAction)
        #file.addAction(self.exitAction)

    def initTextLines(self):
        
        surnameLine = QLineEdit(self) 
        nameLine = QLineEdit(self) 
        streetLine = QLineEdit(self) 
        numberLine = QLineEdit(self) 
        postalCodeLine = QLineEdit(self) 
        
        surnameLine.setGeometry(110, 50, 150, 20)
        nameLine.setGeometry(110, 80, 150, 20)
        streetLine.setGeometry(110, 110, 150, 20)
        numberLine.setGeometry(110, 140, 150, 20)
        postalCodeLine.setGeometry(110, 170, 150, 20)

        surnameLine.textChanged.connect(self.eventManager[3])
        #surnameLine.editingFinished.connect(self.eventManager[3])

        
    def initComboBox(self, cityCache):
        
        # City choice
        
        self.tmp = cityCache
        self.cityBoxContent = ''
        cityBox = QComboBox(self) # Stadt
        cityBox.setGeometry(110, 200, 150, 20)
        #cityBox.setEditable(True)
        #cityBox.clearEditText()
        
        #cityBox.setLineEdit() #.setMaxLength(45)
        #cityBox.setCompleter()
        #cityBox.addItem('')
        cityBox.addItems(cityCache)
        cityBox.currentIndexChanged.connect(self.eventManager[2])
         
    def initTextLabels(self):
        
        textLine1Label = QLabel(self)
        textLine1Label.setGeometry(40, 50, 70, 20)
        textLine1Label.setText("Vorname: ")
        #textLine1.setStyleSheet('QLineEdit {background-color: #daf982}')
        
        textLine2Label = QLabel(self)
        textLine2Label.setGeometry(40, 80, 70, 20)
        textLine2Label.setText("Nachname: ")
        
        textLine3Label = QLabel(self)
        textLine3Label.setGeometry(40, 110, 70, 20)
        textLine3Label.setText("Straße: ")
        
        textLine4Label = QLabel(self)
        textLine4Label.setGeometry(40, 140, 70, 20)
        textLine4Label.setText("Hausnummer: ")
        
        textLine5Label = QLabel(self)
        textLine5Label.setGeometry(40, 170, 70, 20)
        textLine5Label.setText("PLZ: ")
        
        textLine6Label = QLabel(self)
        textLine6Label.setGeometry(40, 200, 70, 20)
        textLine6Label.setText("Stadt: ")
    
    def initButtons(self):
        
        confirm = QPushButton(self)
        #btnConfirmEvent = self.eventManager[1]
        confirm.setGeometry(25,250,100,30)
        confirm.setText('Bestätigen')
        #btnConfirm.clicked.connect(btnConfirmEvent('3'))
        confirm.clicked.connect(self.eventManager[1])

        logOff = QPushButton(self)
        logOff.setGeometry(175,250,100,30)
        logOff.setText('Abmelden')
        logOff.clicked.connect(self.eventManager[0])
    
        
    def userInterface(self, cityCache):
        self.setStyleSheet('QMainWindow {background-color: #caff33}')
        self.setGeometry(250 , 300 , 300 , 500)
        self.setWindowTitle('Sportimer')
        
        #self.initActions()
        self.initMenubar()
        self.initTextLines()
        self.initComboBox(cityCache)
        self.initTextLabels()
        self.initButtons()
        self.show()
        
        
#==============================================================================#
# Controller                                                                   #
#==============================================================================#        
                       
class Control(object):
    
    def __init__(self):
        
        eventManagement = [self.logOff, self.confirm, self.cityBox, \
                           self.textlineChanged]
        self.view = View(eventManagement)
        self.model = Model()
        self.cache = []
        self.textLine1 = ''
        self.textLine2 = ''
        self.textLine3 = ''
        self.textLine4 = ''
        self.textLine5 = ''
    
    
    def initModel(self):
        self.model.db_connect('dbname=sportimer user=postgres \
                                host=localhost password=50/5x5+5-55')
        self.model.loadCities()
        self.model.db_close()
        self.cache = self.model.getCache()
        
    def initUI(self):
        self.view.userInterface(self.cache)
        
    def logOff(self):
        print('Funzt')
    
    def confirm(self):
        print('XXXXX')

    def textlineChanged(self, text):
        j = ''
        for i in text:
            j += i
        print(j) 

    def cityBox(self, i):
        #print(self.view.cityBox.currentText(i))
        print(self.cache[i])
        

if __name__ == '__main__':  
    app = QApplication(sys.argv)          
    start = Control()
    start.initModel()
    start.initUI()
    sys.exit(app.exec_())

    
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

Habe mir gerade mal den Thread MVC ohne QT-Rahmenwerk durchgelesen und nun das Gefühl, ein bisschen auf dem Holzweg zu sein. Aber erst mal weiter so.

Ich wollte mein Problem noch einmal genauer erläutern:

Ich habe 5 QEditLines, deren Eingaben ich auslesen will. Ich will dafür aber nur eine Funktion benutzen. Wenn ich nun diese Funktion aufrufe, wie teile ich ihr dann mit, für welches Eingabefeld sie aufgerufen wird. Derzeit rufe ich sie über ein Liste auf, welche im Controller implementiert ist.

surnameLine.textChanged.connect(self.eventManager[3])
nameLine.textChanged.connect(self.eventManager[3])
streetLine.textChanged.connect(self.eventManager[3])
numberLine.textChanged.connect(self.eventManager[3])
postalCodeLine.textChanged.connect(self.eventManager[3])

Derzeit fällt mir nur die Lösung ein, für jedes Textfeld eine eigene Funktion zu schreiben, wie z. B.:

def textline1Changed(self, text):
self.textLine1 += text

Wo liegt mein Denkfehler?
BlackJack

@the_real_noob: Ein grosses Problem was ich persönlich hier habe ist MVC. Man braucht es in ”Reinform” selten und jeder versteht etwas anderes darunter. Ein ”ich übe MVC”-Projekt ist in den allermeisten Fällen unsinnig komplex es sei denn man braucht dies komplette Trennung aller *drei* Komponenten *tatsächlich*. Und selbst dann: Wenn man die Klassen `Model`, `View`, und `Control` (eigentlich ja `Controller`) nennt, dann riecht das auch komisch, denn so nennt man die ja in echt nie. In Qt findet man den *Zusatz* `SomethingModel` und Views sind ja eigentlich Widgets, also findet man dafür dann manchmal Namen wie `FrobnicationWidget` oder so. Einen Controller würde ich mit in das Widget packen, ausser es macht tatsächlich Sinn den dort heraus zu ziehen weil sich mehrere Widgets einen Controller teilen können. Wobei dass dann wieder darauf ankommt wie ”dumm” das Model ist, und da wären wir dann wieder bei der Frage was MVC eigentlich genau ist.

Dein Model ist IMHO keins. Das ist bloss eine sinnlose Klasse die die Daten aus der Datenbank lädt. Einmal. Das hätte man viel einfacher in einer Funktion haben können.

Die Fehlerbehandlung dort ist auch keine. Du erstellst die Verbindung, falls das nicht klappt wird ein Text auf der Konsole ausgegeben, die man bei einer GUI-Anwendung in der Regel überhaupt nicht sieht, und der Code der das aufruft macht auch bei einer fehlgeschlagenen Verbindung weiter als sei nichts geschehen. An der Stelle wäre es sauberer die ”Fehlerbehandlung” einfach komplett weg zu lassen. Ähnlicher effekt, aber weniger Code und mehr Information auf der Konsole um den Fehler zu finden.

Die API vom Model ist auch ein bisschen komisch. Nach `loadCities()` wird man doch immer danach `getCache()` aufrufen. Einen Cache implementiert man normalerweise so das der Benutzer davon nichts explizit mitbekommt. Da wäre also eher eine `get_city_names()`-Methode die entweder die Namen lädt oder aus dem Cache holt als API zu erwarten. `getCache()` ist ein trivialer Getter den man in Python eher nicht schreiben würde. Und `cache` ist als Name zu generisch.

Statt den Kommentar ``# Nur Stadt anhängen, nicht id`` zu der magischen 1 als Index in den Code zu schreiben, hätte man auch einfach nur die Städtenamen von der Datenbank abfragen können. ``*`` ist da sowieso nicht so gut, eben weil man nicht so ohne weiteres weiss was das alles ist, und weil das von der Reihenfolge der Spalten in der Datenbank abhängt. Wenn man da mal etwas ändert, oder auch welche entfernt oder einfügt, darf man durch den ganzen Quelltext gehen und magische Indexzahlen anpassen.

Apropos magische Indexzahlen: Was soll dieser `eventManager`-Quark? Das ist ein komischer Name für eine simple Liste mit Methoden als Elementen und ``self.eventManager[3]`` ist nicht wirklich verständlich. Mann muss dann erst einmal suchen was das vierte Element in der Liste für eine Bedeutung hat.

Man würde die Verbindung auch eher nicht im Widget vornehmen sofern sie nicht ”intern” gebraucht werden, sondern der Controller verbindet was er braucht.

`tmp` ist ziemlich wahrscheinlich kein Attribut für ein Objekt. Attribute sollen den Zustand eines Objekts ausmachen. Irgendeine temporäre Hilfsvariable gehört da bestimmt nicht dazu.

`setGeometrie()` ist Qt's Tk-Äquivalent zu `place()`. Das verwendet man nicht. Dafür gibt es auch in Qt Layouts die sich darum kümmern.

Die ganze Initialisierung sollte des Widgets sollte auch nicht von aussen angestossen werden. Das passiert üblicherweise durch die `__init__()`. Nach dem die `__init__()`-Methode eines Objekts durchgelaufen ist, sollte es in einem benutzbaren Zustand sein. Gilt dementsprechend auch für `Control`, und ich würde das wahrscheinlich auch auf `Model` anwenden.

Durchnummerierte Namen sind ein „code smell“. In `initTextLabels()` würde ich einfach immer `label` für alles benutzen. Die verschiedenen Namen werden doch überhaupt nicht gebraucht. Bei `Control` möchte man wohl für die `textLine*`-Attribute wohl eher eine Liste verwenden.

Wenn `Control` sowohl `View` als auch `Model` hart verdahtet kennt und erstellt, dann ist das wieder ein Zeichen das diese Trennung keinen Sinn macht. Denn dann kann man ja weder das Model noch den View austauschen.

Der Code von `textlineChanged()` macht keinen Sinn. Da hätte man auch gleich ``print(text)`` schreiben können. Und die Namen `i` und `j` für Zeichenketten sind keine gute Idee.  Das sind typische Indexvariablennamen und somit erwartet jeder Programmierer ganze Zahlen.

Zum eigentlichen Problem: Man könnte der Rückrufmethode noch ein erstes Argument verpassen das den Index in die Liste mit den Zeilen darstellt und beim Binden dann mit `functools.partial()` arbeiten.

Wobei: Will man wirklich das jede kleine Textänderung sofort ins Model übertragen wird? Üblicherweise hat man dafür doch eine ”Übernehmen”-Schaltfläche die die Änderungen dann tatsächlich speichert. Kann auch automatisch beim Wechseln des Datensatzes passieren.

Letzendlich ist mir das alles ein wenig zu GUI-lastig und ich sehe wenig auf der Datenhaltungs/Logik-Seite. Nur müsste man die ja erst einmal haben bevor man sagen kann ob die GUI die man draufsetzt, so sinnvoll ist. Normalerweise fängt man insbesondere bei Projekten mit einer Datenbank ja erst einmal damit an welche Daten man hat, was man damit machen will, und wie dann die Strukturen und Operationen aussehen müssen. GUI würde ich am Projektanfang höchstens als Prototyp machen. Damit man ein Gefühl dafür bekommt welche Informationen der Benutzer sehen muss und welche Operationen angeboten werden müssen. Das aber alles ohne MVC sondern nur eine zusammengeklickte GUI ohne tatsächliche Funktionalität, oder höchstens so viel wie man für Demozwecke braucht um mit dem Kunden darüber sinnvoll reden zu können.
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

Danke, BlackJack, für Deine Mühe.

Du siehst mich allerdings im Augenblick ein wenig überfordert, was Deine Ausführungen angeht. Manches habe ich verstanden, manches nicht. Muss ich erst einmal sacken lassen.

Mein Ansatz war, mir erst einmal eine GUI zusammenzubasteln, mit der ich dann die Datenbank befülle, um anschließend ein bisschen mit den Datensätzen zu spielen und sie mir in einer Tabelle oder ähnliches ausgeben zu lassen.

In meiner Datenbank befindet sich im Moment nur eine Tabelle mit Städtenamen. Alles andere ist im Werden.

Eine Idee ist, die QComboBox editierbar zu machen und nach drei eingegebenen Buchstaben eine Auswahl an Städtenamen in der QComboBox ausgeben, die immer weiter verfeinert wird.

Grundsätzlich wollte ich meine Klassen weiter aufdröseln, so zum Beispiel abstrakte Klassen programmieren und dann die einzelnen Controller-Aufgaben in weiteren Klassen ableiten.

Da Du aber von MVC abrätst, welchen Ansatz sollte ich Deiner Meinung nach wählen, um das zu implementieren?
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@the_real_noob: im Normalfall reicht eine einfache Trennung der Geschäftslogik von der GUI, heißt, ich kann die gesamte Funktionalität ohne GUI testen und bedienen. Darauf aufbauend ist die GUI-Schicht recht dünn; abstrakte Klassen und Ableitung hört sich stark nach Java und nicht so sehr nach Python an.
BlackJack

@the_real_noob: Gerade Datenbankentwürfe sind etwas was man in der Regel nicht ”agil” entwickelt. Weil Änderungen daran mit mehr Aufwand verbunden sind. Und für eine GUI muss man doch erst einmal wissen was sie darstellen und manipulieren können muss. GUI kann wie gesagt am Anfang sinnvoll sein um einen Prototyp zu haben und Daten und Operationen zu erfassen. Allerdings auch nur wenn man so etwas wie einen Kunden hat der sich viel unterhalb der Oberfläche vorstellen kann oder will und mit dem man Darstellungen und Arbeitsabläufe abklären muss.

Das echte Programm mit der GUI anzufangen wäre da sowas wie das Haus mit dem Dach anzufangen, und dann die Mauern unter das freischwebende Dach von oben nach unten zu Mauern. Der GUI-Prorotyp für den Kunden ist da eher so etwas wie ein Modell vom Gebäude, so dass man sich das fertige Haus besser vorstellen kann.

Ich würde damit anfangen die Daten zu modellieren und die Programmlogik. Wenn das funktioniert, kann man die GUI oben drauf setzen.

Ich rate nicht generell von MVC ab. Aber das ist nur *ein* Werkzeug von vielen und man sollte das nur einsetzen, wenn man es braucht. Entwurfsmuster lösen Probleme und sind kein Selbstzweck. Es macht keinen Sinn bei GUI zu denken ”oh ja, das mache ich alles mit MVC”. Dann hat man eine ”Lösung” und versucht das Problem passend zu formulieren. Das stellt die Verhältnisse auf den Kopf.

Was man machen sollte ist die Programmlogik von der GUI zu trennen. Damit die Logik beispielsweise ohne GUI testen kann. Oder auch ohne GUI verwenden kann, oder mit einer anderen GUI. Also die klassische Trennung zwischen Logik und Benutzerinteraktion.

Bei der Beschreibung von den Klassen und wie Du die weiter aufteilen möchtest, habe ich zumindest den Verdacht, dass das in die falsche Richtung geht. Und Klassen sind auch kein Selbstzweck. Wenn eine Funktion reicht, muss man die nicht mit aller Gewalt in eine Klasse stecken. Wie schon gesagt ist Deine `Model`-Klasse so wie sie verwendet wird im Grunde nur eine recht kompliziert ausgedrückte Funktion.

Bei der Klasse fällt mir auch auf/ein, dass ich ein ORM verwenden würde für die Datenbank, also in Python SQLAlchemy (wenn es keine Django-Anwendung ist).
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

Vielleicht sollte ich mal sagen, dass das alles gar keinen anderen Sinn hat, als ein bisschen zu üben, um sich mit verschiedenen Werkzeugen vertraut zu machen. Ich spiele halt einfach herum, ohne im Sinn zu haben, dass alles am Ende zu einem konkreten Ergebnis zu führen. Mir geht es in aller erster Linie darum zu lernen und die Funktionalität zu verstehen.

Mein Problem ist, ich kann nicht gut linear lernen. Ich habe mir das Python 3 Buch vom MITP Verlag besorgt. Gar nicht schlecht, aber was soll ich die Fibonacci-Folge programmieren oder gar Threads, wo ich doch wahrscheinlich nie die Datenmenge verarbeiten muss, die bei modernen Prozessoren Nebenläufigkeit benötigt?

Nee, für mich ist der Weg 'Learning by doing' der einzig geeignete. Und das Buch ist ein Hilfsmittel dazu.

Die Aufgabe, die ich mir gestellt habe, ist eigentlich ganz einfach. Ich habe eine Datenbank, die ich mit den Daten Vorname, Name, Straße, Hausnummer, PLZ und Stadt befüllen will. Dazu soll eine GUI dienen, in welche die Daten eingegeben und durch die Betätigung des Bestätigen-Buttons in die Datenbank eingetragen werden. Danach wird die GUI geleert und der nächste Datensatz kann eingegeben werden. Bei Betätigung des Abmelde-Buttons soll die Datenbank geschlossen, Aufräumarbeiten durchgeführt und das Programm geschlossen werden.

Wenn das erfolgreich implementiert ist, wäre die nächste Aufgabe, eine GUI zu erstellen, in denen mehrere Buttons die Möglichkeit bieten, unterschiedliche SQL-Abfragen durchzuführen und die aus der Datenbank ausgelesenen Daten in einer Tabelle darzustellen.

Das ist alles.

Das alles aber natürlich aber auch mit dem Anspruch, dass möglichst professionell zu machen. :D

Deswegen habe ich mich für PyQt anstelle von TKinter entschieden, wobei mir mittlerweile Zweifel gekommen sind, ob das wirklich eine gute Idee gewesen ist und zwecks Implementierung für das MVC-Muster.

Mir gefällt beim MVC die Strukturierung und die klare Aufgabenaufteilung. Ich vergleiche das so ein bisschen mit der Geschichte in der Arbeitswelt. Früher hatte man Allrounder, die alles konnten und wenig produzierten, danach Spezialisten, die nur noch Teilaufgaben im Produktionsprozess übernahmen und gemeinsam viel herstellten.

Der große Vorteil von MVC ist zudem die Wartbarkeit des Codes. Je umfangreicher ein Programm wird, desto unüberschaubarer wird es, vor allem, wenn mehrere Leute am Entwicklungsprozess beteiligt sind. Klar, für mein kleines Projekt mag das überdimendioniert sein, aber irgendwie muss man das ja lernen.

Ich glaube auch, dass viele Programmierer das MVC gar nicht richtig beherrschen oder die klare Trennung der jeweiligen Funktionalität missachten, weil es sich einfach schneller oder einfacher umsetzen lässt (wobei das vielleicht eben nur deswegen so ist, weil sie das MVC ungenügend beherrschen). Wahrscheinlich ist die unsaubere Programmierung vieler Programmierer auch einer der Hauptgründe für die Kritik am MVC. Was bringt schon eine 50-60% Umsetzung, wenn man am Ende doch wieder den ganzen Programmcode durchsuchen muss, um eine Funktion zu finden?

Vielleicht ist das MVC auch deswegen schon in vielen Projekten obsolet, weil heutzutage Frameworks vieles erledigen, was der Programmierer sonst selbst implementieren und vor allem warten müsste. Aber auch QT hat MVC-Elemente implementiert - ich habe zwar noch nicht verstanden was und wie, aber das soll so sein.

Nun habe ich heute viele Stunden damit verbracht, zu versuchen, die Events der QLineEdit-Objekte abzufangen und bin kläglich gescheitert, weil ich immer noch nicht weiß, wie ich das Objekt erkennen soll, das das Event feuert. Es gibt zwar vom QObject die Funktion sender(), aber die habe ich nicht vernünftig implementiert bekommen.

Ich glaube, der nächste Schritt für mich ist, dass ganze Programm erst einmal auseinanderzureißen und die Klassen tatsächlich auch in eigene Module zu packen und BlackJacks Anmerkung über meine verwandten Bezeichner beherzigen.

Mein Ansatz mit den abstrakten Klassen, habe ich tatsächlich von Java (vor 15 Jahren habe ich mal damit rumgespielt). Ich finde die Idee gut, dass man eine abstrakte Basisklasse hat, die quasi als Dach für die Unterklassen fungiert. Denn man hat zwar Klassen, in denen man zusammenhängende Funktionen zusammenfassen kann, aber auch viele Funktionen, die quasi allein in der Luft hängen und die man eben in eine Funktionensammlung unterbringen kann. Auch kann man eben abstrakte Funktionen in der Basisklasse definieren, die dann in den Unterklassen überschrieben werden.

Ich denke, eine abstrakte Klasse ist genauso wie das MVC vor allem ein ordnendes Element.
BlackJack

@the_real_noob: Wenn das ganze keinen Sinn hat, dann kann man auch nicht lernen wie man die Werkzeuge verwendet. Denn egal ob nun Klasse überhaupt, (abstrakte) Basisklasse, MVC, oder was auch immer: Es gilt auch zu lernen wann man diese Dinge *nicht* verwendet.

Threads sind nicht nur für grosse Datenmengen interessant. In CPython schon mal gar nicht wegen dem „global interpreter lock“ (GIL). Da würde man Prozesse verwenden (`multiprocessing`-Modul). Nebenläufigkeit mit Threads braucht man in Python wenn man mehrere Sachen parallel ausführen möchte, wie beispielsweise etwa längerfristiges in einem GUI-Programm ohne das die GUI einfriert, oder bei einem Raspberry Pi wenn man mehrere Ein- und Ausgaben an den GPIOs parallel durchführen möchte.

Das Dir beim MVC die klare Aufgabenaufteilung gefällt, hat so ganz allgemein eine gewisse Ironie, weil die nämlich nicht wirklich klar ist. Das mag für konkrete Rahmenwerke klar sein, aber allgemein ist MVC sehr schwammig. Deswegen hat auch jedes Rahmenwerk seine eigene Idee davon was MVC konkret bedeutet.

Und noch einmal: MVC ist ein Werkzeug um ein bestimmtes Problem zu lösen. Und wenn man dieses Problem gar nicht hat, dann braucht es auch diese Lösung nicht, und folglich kann man MVC auch gar nicht damit üben. Denn eine erfolgreiche Anwendung kann man ja nur üben und den Übungserfolg überprüfen, wenn man das Problem klar benennen und die Lösung durch MVC belegen kann. Wenn ein Werkzeug überdimensioniert ist, dann ist es das falsche Werkzeug und man hat eher etwas gelernt was man *nicht* machen sollte.

MVC, oder auch irgendein anderes Entwurfsmuster führen nicht automatisch zu wartbarem Code. Es kann auch das Gegenteil passieren wenn das Muster nicht oder nicht gut zum Problem passt, oder es gar kein Problem gibt, welches durch das Entwurfsmuster gelöst würde. Dann handelt man sich einfach nur unnötige Komplexität ein, was die Wartbarkeit verschlechtert und unnötige Angriffsfläche für Fehler bietet.

Einer der Hauptgründe für die Kritik an MVC ist, dass es nicht *das* MVC gibt, sondern recht viele Interpretationen davon, und dass es deswegen zu Problemen kommt wenn unterschiedliche Interpretationen zusammenprallen. Also beispielsweise wenn Programmierer A eine Vorstellung hat, die der von Programmierer B kollidiert, und da nicht bemerkt wird bis es zu konkreten Unstimmigkeiten führt.

Auch Qt hat seine Vorstellung von MVC. Gerade wenn Du nicht weisst welche, dann solltest Du kein MVC nach Deinen Vorstellungen implementieren und das so nennen, denn es könnte ja sein, dass es ein anderes MVC ist als Qt umsetzt und schon haben wir genau die Situation das Programmierer A — Du — eine andere Vorstellung hat als Programmierer B — die Qt-Entwickler. Und dann kommt ein Programmierer C und schaut sich Deinen Code an, liest ”MVC” und kommt durcheinander weil er von Qt ausgeht, aber Dein MVC anders aussieht und seine Annahmen nicht stimmen. Oder er hat vorher mit Rahmenwerk XY gearbeitet und das hatte eine Idee von MVC die sich von Deinem und von Qt's Ansatz unterscheiden. Prost Mahlzeit. :-)

Die `sender()`-Methode kann/braucht man nicht implementieren, die gibt es ja bereits. Man muss sie aufrufen. Ich weiss gar nicht ob das von Python aus geht beziehungsweise welche Voraussetzungen dafür erfüllt sein müssen. Ich vermute mal das die Methode mindestens in einer von `QObject` abgeleiteten Klasse definiert sein muss. Selbst wenn man sie auf anderen Exemplaren aufrufen kann, deren Typ von `QObject` abgleitet wurde, sähe das sicher etwas komisch aus. Vielleicht muss man die Methode dafür sogar explizit als Slot ”deklarieren”, statt den dynamischen Mechanismus der Qt-Anbindung zu verwenden die ja jedes beliebige aufrufbare Objekt als Emfänger eines Signals verbinden kann.

Wäre mir aber egal, ich habe in solchen Fällen immer eine IMHO einfachere Python-Lösung gewählt: `functools.partial()` um Argumente der Rückruffunktion an einen Wert zu binden, der für die jeweilige Verbindung unterschiedlich ist. Das ist in Python leichter und universeller, da überhaupt nicht von Qt abhängig. Die gleiche Lösung kann man in Qt, Gtk, Tkinter, oder auch in anderen Kontexten als GUIs verwenden.

Die letzten drei Absätze lassen mich daran zweifeln ob Du Python programmieren willst. Python ist nicht Java. Module sind eine Strukturierungseinheit wie in Java Packages. Du wirdest in Java doch auch nicht jede Klasse in ihr eigenes Package stecken. Genausowenig macht es in Python Sinn jede Klasse in ihr eigenes Modul zu stecken.

Funktionen die quasi allein in der Luft hängen oder gar Funktionssammlungen gehören gar nicht in Klassen. Nicht in abstrakte Basisklassen und auch nicht nicht in andere Klassen. Funktionen gehören in Module. In Java sind die in Klassen weil Java keine Funktionen kennt und man deshalb gezwungen ist Funktionen als Methoden in Klassen zu stecken, weil man in Java alles in Klassen stecken muss. Zum einen ist Javas klassenfixierte Sicht von objektorientierter Programmierung recht eingeengt, zum anderen unterstützt Python auch das funktionale Paradigma. Und das sollte man auch nutzen. Wenn eine Funktion ausreicht, sollte man nicht künstlich eine Klasse drum herum bauen.

Wirklich rein abstrakte Klassen würde ich in Python nicht schreiben. Und abstrakte Methoden nur wenn die in der Klasse von anderen Methoden aufgerufen werden, damit in diesem Fall eine passendere Ausnahme als `AttributeError` kommt wenn die Methode in einer abgeleiteten Klasse nicht implementiert wurde.

Wenn Du abstrakte Klassen als ordnendes Element möchtest, dann würde ich wieder Java empfehlen. Dort *kannst* Du Dich mit solchen Ordungen, Klassifizierungen, und Hierarchien so richtig schön austoben. Und Du *musst* Dich mit diesem ganzen Überbau auseinandersetzen, denn da ist ja alles auf diese Weise so schön geordnet. Zu Python und ähnlichen Sprachen kommen Leute die diesen ganzen „discipline & bondage“-Kram nicht so sehr mögen. Ich war am Anfang von den Typhierarchien und Interfaces in Java auch fasziniert — das man alles so schön kategorisieren kann. Bis man dann an den Punkt kommt, wo diese Ordnung anfängt Probleme zu bereiten, weil nicht alles immer so schön sauber einordnenbar ist, insbesondere wenn es in bereits bestehende Hierarchien passen muss. Das führt dann zu Code der nur dazu dient etwas in dieses Korsett zu zwängen, aber gar nicht zur eigentlichen Problemlösung beiträgt. Was war ich damals dann von „duck typing“ in Python begeistert. :-)
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

Dennoch sind abstrakte Klassen in Python nicht unbekannt, schließlich gibt es die Abstract Base Classes und das schon seit 2007.

Ich denke, die Python-Core Entwickler überlassen es dem Programmierer, wie er etwas implementieren will. Für den einen ist das MVC zu mächtig, für den anderen ist es SQLAlchemy. Da wird jeder so seinen eigenen Weg finden müssen.

Mir sind Glaubenskriege fremd. Bloß weil etwas in Java gemacht wird, muss es doch nicht gleich in Python schlecht sein. Ich habe heute ein Progammierbeispiel für ein in Python programmiertes Observer-Pattern gefunden. Da schrieb dann doch tatsächlich ein Pythonista in die Kommentare, dass sich so ein Java-Programmierer Python vorstellt, wo ich mir die Frage stellte, was das denn für ein blödsinniger Kommentar ist. Nur weil Java eine Observer-Klasse von vornherein implementiert hat, muss man auf das Observer-Pattern in Python verzichten? Schlimm genug, dass man es selber schreiben muss, wo es doch schon Ewigkeiten existiert.

Ich nehme mir doch von allem das Beste und selbstverständlich ist das Duck Typing ein Segen, wenn man eine Integer-Zahl dividiert und nicht extra eine Hilfsvariable anlegen muss, wenn das Ergebnis ein float ist, aber deswegen verzichte ich doch nicht auf elementare Designelemente.

Klar wird das MVC unterschiedlich interpretiert, zumal es sich in der Webprogrammierung gar nicht 'rein' implementieren lässt, weil auf der Client-Seite die Ereignisse mit Java-Script abgearbeitet werden, weswegen hier die Abwandlung MVP zu Geltung kommt und natürlich kommt man an einer ausführlichen Dokumentation nicht vorbei, wenn man in einem Team arbeitet oder nachfolgende Programmierer nicht vor unlösbare Aufgaben stellen will.

Ich bin nun kein Ordnungsfanatiker, bei mir stapelt sich die Wäsche und das Geschirr auch schon mal, der Boden müsste mal wieder gewischt werden, aber wenigstens das Klo ist sauber. Aber es gibt Dinge, da ist mir Ordnung wichtig, wie z. B. in der Werkstatt. Da hat das Werkzeug seinen festen Platz - das Werkzeug, die Maschinen, das Material usw.. Ich hasse nichts mehr, als einen Zollstock suchen zu müssen. Genauso ist es mit meinem Rechner. Ich habe lediglich eine Shutdown-, eine Restart- und die Papierkorb-Verknüpfung auf dem Desktop liegen. Alles andere ist ordentlich im Startmenü sortiert und geordnet. Wenn ich mir manchmal Rechner von anderen zu Gemüte führen, da werde ich verrückt. Damit kann man doch nicht effektiv arbeiten.

Und genau diese Haltung habe ich auch gegenüber dem Programmieren.

Ich verstehe aber auch Deine Kritik an einem zu starren Korsett und das man die Flexibilität bewahrt werden muss. Da muss man dann halt wieder eine eierlegende Wollmilchsau stricken.

Danke für den Hinweis mit dem Threading. War mir nicht klar, dass man es auch dafür benutzen kann. Gerade das mit den GUIs wusste ich gar nicht.

Ich bastel mal weiter.
BlackJack

@the_real_noob: Es gibt die ABCs sein 2007, und doch werden sie kaum verwendet.

MVC ist nur zu mächtig wenn es kein Problem löst sondern nur um seiner selbst willen den Code unnötig komplexer, und damit schwerer nachvollziehbar und fehleranfälliger macht. Ein doch relativ einfaches Entwurfsmuster mit einer kompletten Bibliothek zur Datenbankabstraktion und ORM zu vergleichen ist nicht sinnvoll.

Man kann das aber auch umdrehen: Nicht alles was in Java gemacht wird ist auch in Python gut. Und auch umgekehrt sollte man in Java nicht so wie in Python programmieren.

Wieso ist das ein blödsinniger Kommentar wenn jemand sagt, dass die konkrete Implementierung des Observer-Patterns nach Java aussieht und nicht nach Python. Unsinnig ist eher Dein Schluss daraus das man das in Python entweder wie in Java oder gar nicht implementieren kann. Python und Java sind ziemlich unterschiedliche Programmiersprachen mit unterschiedlichen Eigenschaften. Viele Entwurfsmuster sind auf Sprachen wie Java zugeschnitten. Das heisst nicht das man die in Sprachen wie Python nicht auch Anwenden oder finden könnte, nur sehen die da eben anders aus. Obwohl einige tatsächlich ”verschwinden”, weil aus den Entwurfsmustern eher Idiome werden, da das Muster von der Sprache schon implementiert ist. Python operiert zum Beispiel *sehr viel* mit Iteratoren, was ja in Java auch ein Entwurfsmuster ist. In Python kommt man um die Verwendung von Iteratoren überhaupt nicht herum, denn man kommt kaum um ``for``-Schleifen herum und und die können nichts anderes als Iteratoren von iterierbaren Objekten abfragen. Zudem gibt es unmengen an Funktionen und Methoden die iterierbare Objekte liefern oder konsumieren. Trotzdem ist es sehr selten das man in Python tatsächlich eine Klasse schreibt die das Iteratorprotokoll implementiert denn es gibt die ``yield``-Anweisung mit der man das viel einfacher und kompakter lösen kann. Und damit wird zumindest ein Teil des Entwurfsmusters zu einem Idiom und wenn jemand ohne Not einen Iterator als Klasse implementiert, dann würde ich auch sagen, das ist wahrscheinlich jemand der von einer Sprache wie Java kommt, und dass diese Klasse kein idiomatisches Python ist.

Zur konkreten Umsetzung des Observer-Patterns auf das Du Dich gerade beziehst, kann ich nichts sagen, weil ich sie nicht kenne. In Python würde man aber erst einmal schauen ob eine Rückruffunktion oder -methode ausreichend ist. Was in Java nicht möglich ist, beziehhungsweise nicht möglich war, denn mit Java 8 sind ja ein paar nette Sachen dazu gekommen um Boilerplate-Code zu vermeiden. Falls eine Rückruffunktion nicht ausreicht, kann man sich *einmal* ein generisches ”observable” schreiben und das einfach verwenden. Entweder als Mixin-Klasse (sowas geht in Java nicht) oder per Komposition. Das hat den Vorteil, dass man das von aussen an jedes aufrufbare Objekt übergeben kann, welches nur eine einfache Rückruffunktion erwartet, und das man von einer Klasse aus mehr als ein ”observable” Nachrichten/Ereignisse verschicken lassen kann. Also so etwas wie das Signal/Slot-Konzept von Qt, was ja auch eine Umsetzung des Observable-Pattern ist. Das kann man in Python *einmal* sehr generisch implementieren. Könnte beispielsweise so aussehen:

Code: Alles auswählen

class Callbacks(object):

    def __init__(self):
        self.items = set()
        self.add = self.items.add
        self.remove = self.items.remove
        self.clear = self.items.clear

    def __len__(self):
        return len(self.items)

    def __iter__(self):
        return iter(self.items)

    def __call__(self, *args, **kwargs):
        for callback in self:
            callback(*args, **kwargs)
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

Du haust mir in dem letzten Absatz Deiner Antwort ein paar Begriffe um die Ohren, mit denen ich noch nicht so recht etwas anzufangen weiß. Steile Lernkurve, die Du mir da abverlangst.

Aber unabhängig davon, bin ich so ein bisschen kritisch, wenn man mir versucht, irgendwelche 'Wahrheiten' zu verkaufen. Da kommt eine ganze Menge Unsinn bei raus. Zum Beispiel:

In dem Python 3 Buch des MITP Verlages, schreibt der Autor Michael Weigand, dass die __init__()-Methode ein Konstructor sei und er behandelt sie auch so, sprich, er ruft Methoden in ihr auf. Nun bin ich zufälligerweise über ein Video gestolpert, in dem Raymond Hettinger, seines Zeichens Core-Entwickler von Python vor einer versammelten Pythonista-Gemeinde erklärt, dass die __init__()-Methode eben kein Konstruktor sei, sondern eine Methode ist, mit der die Variablen eines Objektes der Klasse initialisiert werden, während die oberhalb dieser Methode liegenden Variablen allen Objekten der Klasse gehören. Guckst Du und liest Du hier.

Jetzt muss man sich mal vor Augen führen, wie viele Leute einem erzählen, die __init__()-Methode ist ein Konstruktor und davon felsenfest überzeugt sind.

Dann kommst Du und erzählst mir was von wegen dem MVC und das die abstrakten Basisklassen nicht benutzt werden. Da Du mir nun so von SQLAlchemy vorgeschwärmt hast, obwohl ich ein derart großes Framework für mein kleines Projekt als völlig überdimensioniert ansehe (bevor ich mich damit vertraut gemacht habe, habe ich die paar SQL-Abfragen 100mal selbst geschrieben), sah ich mir mal auf der Webseite von SQLAlchemy ein Video dazu an.

Mike Bayer, der Entwickler von SQLAlchemy, erklärt darin (ab 5:36 min) , dass er ein Model-Ordner anlegt, indem er Meta-Module als abstrakte Klassen anlegt. Zumindest habe ich das jetzt so verstanden.

Also ist mein Weg doch gar nicht so verkehrt.

Du solltest vielleicht einmal bei der Erörterung meiner Probleme nicht davon ausgehen, wie man etwas macht bzw. Deiner Meinung nach macht, sondern wie man etwas lernt. Ich will lernen, wie man das MVC implementiert, dabei ist es vollkommen unerheblich, ob man die angenommene Problemstellung auch anders bzw. einfacher lösen könnte.

Ansonsten nimmt das ganze Konstruktive das Du dankenswerter Weise einbringst etwas destruktives an.

Ansonsten eine Frage: Du gibst als Wohnort Berlin an und ich bin nativer Charlottenburger. Engagierst Du Dich bei der Berliner Python User Group?
BlackJack

In den Hettinger-Notizen zu seinem Vortrag steht unter anderem auch dieser Satz: „Don't impose Java rules when you are coding in python and vice versa.“ :-)

Das `__init__()` oft als Konstruktor bezeichnet wird, liegt daran das a) diese Art von Methode in anderen OOP-Sprachen als Konstruktor bezeichnet wird (z.B. Java), weil die nichts haben was noch *davor* liegt und das Objekt tatsächlich konstruiert und nicht nur initialisiert. Und b) der tatsächliche Konstruktor in Python (`__new__()`) sehr selten verwendet wird und man deshalb mindestens bei Anfängern, diese Unterscheidung oft nicht macht.

Ich sage nicht das abstrakte Basisklassen nicht benutzt werden, das ginge ja gar nicht, denn solche Klassen entstehen — wenn sie denn ein tatsächliches Problem lösen — von ganz alleine, sondern das die aus `abc` und `collections` die rein zum markieren, also zum Ordnen da sind, nicht benutzt werden. Jetzt kannst Du vielleicht Projekte finden die das tun, die sind mir aber bis jetzt noch nicht untergekommen. Ich habe nicht den Eindruck das diese Klassen weite Verwendung finden, denn letztendlich sind sie a) wegen „duck typing“ nicht nötig und b) würde man die ja für Typtests verwenden und das ist ein „code smell“ in der objektorientierten Programmierung.

Ich weiss nicht wie das bei dem aktuellen Trend bei Python 3 mit dem Hang zu immer mehr ”statischer Typisierung” ist. Vielleicht mögen diese Leute das Zeug ja. Ich definitiv nicht, und das wäre auch nicht mehr die Sprache die ich nach Java so erfrischend fand.

In dem Talk von Mike Bayer wird ein Link zu dem dort besprochenen Beispielprojekt angegeben und wenn man nachschaut, habe ich im `model.meta`-Package nur die `Base`-Klasse als abstrakte Klasse gefunden. Und die ist nicht abstrakt nur um irgend etwas zu ordnen, sondern da steckt die ganze ”deklarative Magie” drin, die man braucht, damit man die Abbildung Objekt<->Datenbank das so schön kompakt und verständlich schreiben kann. Es sei denn man würde Mixin-Klassen auch als abstrakte Klassen bezeichnen, davon sind auch ein paar da. Ich würde die aber in eine eigene ”Kategorie” stecken. Aber selbst wenn man die als abstrakte Klassen sieht, sind sie wegen ihrer (partiellen) Funktionalität vorhanden und nicht um etwas zu ordnen oder zu kategorisieren.

SQLAlchemy ist in der Tat ein sehr umfangreiches und sehr flexibles Rahmenwerk und ich würde das auch nicht so heiss für jede Datenbankanwendung empfehlen wenn die DB-API 2.0 und ihre Umsetzung nicht so schlecht wäre. PEP249 weckt die Erwartung, dass man damit *eine* API hat, die von den verschiedenen relationalen DBMS abstrahiert und das man zumindest einfache Operationen, die keine Besonderheiten des jeweiligen DBMS verwenden, damit durchführen kann, ohne zu wissen welches DBMS konkret verwendet wird. Da wird man dann aber schon bei so etwas grundlegendem wie den Platzhaltern für Werte enttäuscht. Oder welche SQL-Typen wie auf Python-Datentypen abgebildet werden. Das die SQLite-Anbindung beispielsweise in den Grundeinstellungen nur TIMESTAMP auf `datetime`-Objekte abbildet, DATE und DATETIME aber nicht. Das ist einfach nur ärgerlich, weil man dann letztendlich doch wieder nur gegen ein spezielles DBMS programmiert, oder sich über die Abstraktionsschicht von PEP249 noch mal eine eigene stricken muss, die diese Unterschiede ausbügelt. Und bevor ich so etwas selber mache, nehme ich lieber etwas gut getestetes, fertiges.

Das zusätzlich dann auch gleich eine API bietet um das manuelle schreiben und zusammenstückeln von SQL-Anfragen als Zeichenketten vermeidet, weil das schnell fehleranfällig und unübersichtlich wird. Zusätzlich hat dann auch gleich einen guten Übergang zu einem ORM.

SQLAlchemy ist zwar sehr umfangreich, aber man muss das ja nicht alles verwenden oder alles auf einmal verstehen. Der Schritt von handgeschriebenen SQL und der DB-API 2.0 ist jedenfalls nicht wirklich kompliziert.

Code: Alles auswählen

records = cursor.execute('SELECT * FROM city').fetchall()
# ->
records = city_table.select().execute().fetchall()
Wenn man sich nicht sicher ist ob da auch das richtige SQL für generiert wird, kann man sich das auch ausgeben lassen:

Code: Alles auswählen

In [29]: print city_table.select()
SELECT city.id, city.name 
FROM city
Ich gehe davon aus wie man etwas lernt. Und wie man etwas nicht lernt. Du kannst ein Entwurfsmuster nur lernen wenn damit auch tatsächlich ein Problem gelöst wird, denn nur dann kann man am Ende überprüfen ob man es richtig gemacht hat. Nämlich daran ob das Problem gelöst wurde oder nicht und wie gut oder schlecht es gelöst wurde. Ein Entwurfsmuster besteht aus einem Namen, einem Problem das es löst, der Beschreibung der Lösung (als Muster), und den Konsequenzen. Und wenn man das Problem nicht benennen kann und nicht über die Konsequenzen reden kann, dann hat man eine Lösung geübt von der man nicht weiss *was* sie überhaupt löst und ob sie es besser oder schlechter löst als eine andere, vielleicht einfachere Alternative. Was ziemlich sinnfrei ist.

Was ein Entwurfsmuster ist und was nicht hängt auch von der Programmiersprache ab. Beispiel dafür im GoF-Buch ist das Vererbung in prozeduralen Programmiersprachen ein Entwurfsmuster ist, in objektorientierten Programmiersprachen nicht, denn dort gibt es Syntax oder Methoden im Sprachkern für die Vererbung. Manche Muster ”verschwinden” in Programmiersprachen in denen Klassen auch Objekte sind, oder die Grenze noch weiter verschwimmt weil es gar keine Klassen sondern nur Objekte gibt. Manche Muster sind in einigen Programmiersprachen einfacher umsetzbar, in anderen schwieriger, bis hin zu so schwierig oder gegen den Rest der vorhandenen Infrastruktur arbeitend, das es sich nicht lohnt das Muster in der Sprache umzusetzen. Man muss bei Mustern auch immer Kosten und Nutzen abwägen. Es ist also durchaus nicht zwangsläufig blödsinnig wenn ein X-Programmierer sagt die Lösung da muss von einem Y-Programmierer sein, weil das Muster wie in Sprache Y umgesetzt ist, aber es in X gar kein Muster ist, weil es dort ein Schlüsselwort dafür gibt, oder es bei (idiomatischer) Verwendung der Sprache ”verschwindet”.

Ich lese die Python-Berlin-Mailingliste, auf der nicht wirklich viel los ist. Das war es dann aber auch schon.
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

BlackJack hat geschrieben:In den Hettinger-Notizen zu seinem Vortrag steht unter anderem auch dieser Satz: „Don't impose Java rules when you are coding in python and vice versa.“ :-)
MVC oder abstrakte Klassen zu benutzen hat aber nichts mit der Programmiersprache zu tun, sondern ist eine Designfrage.
BlackJack hat geschrieben:Ich sage nicht das abstrakte Basisklassen nicht benutzt werden, das ginge ja gar nicht, denn solche Klassen entstehen — wenn sie denn ein tatsächliches Problem lösen — von ganz alleine, sondern das die aus `abc` und `collections` die rein zum markieren, also zum Ordnen da sind, nicht benutzt werden. Jetzt kannst Du vielleicht Projekte finden die das tun, die sind mir aber bis jetzt noch nicht untergekommen. Ich habe nicht den Eindruck das diese Klassen weite Verwendung finden, denn letztendlich sind sie a) wegen „duck typing“ nicht nötig und b) würde man die ja für Typtests verwenden und das ist ein „code smell“ in der objektorientierten Programmierung.

Ich weiss nicht wie das bei dem aktuellen Trend bei Python 3 mit dem Hang zu immer mehr ”statischer Typisierung” ist. Vielleicht mögen diese Leute das Zeug ja. Ich definitiv nicht, und das wäre auch nicht mehr die Sprache die ich nach Java so erfrischend fand.
Diese beiden Aussagen stehen in einem direkten Zusammenhang. Da Python immer mehr in großen Projekten eingesetzt wird, an denen viele Entwickler sitzen und gleichzeitig Python open-source ist, wird schon aus der Richtung der Großentwickler wie Google Druck auf die Core-Entwickler ausgeübt, zumindest Optionen einer strengeren Typisierung anzubieten. van Rossum hat auf der PyCon 2016 in Portland das Problem angesprochen - findest Du bei YT. Besonders begeistert sah er nicht aus - auch andere Features fand er selbst wohl nicht so notwendig und toll.

Und genauso ist es mit den abstrakten Klassen. Das man die nicht inflationär einsetzen sollte, wieder eine Designfrage. Ich finde den Gedanken grundsätzlich durchaus sinnig, dass man quasi einen Container anlegt, der gewisse Funktionalitäten für die darin definierten Klassen vorgibt. Ob das Sinn macht oder nicht, ist dann wahrscheinlich wieder eine Frage der Vorlieben.
BlackJack hat geschrieben:SQLAlchemy ist in der Tat ein sehr umfangreiches und sehr flexibles Rahmenwerk und ich würde das auch nicht so heiss für jede Datenbankanwendung empfehlen wenn die DB-API 2.0 und ihre Umsetzung nicht so schlecht wäre. PEP249 weckt die Erwartung, dass man damit *eine* API hat, die von den verschiedenen relationalen DBMS abstrahiert und das man zumindest einfache Operationen, die keine Besonderheiten des jeweiligen DBMS verwenden, damit durchführen kann, ohne zu wissen welches DBMS konkret verwendet wird. Da wird man dann aber schon bei so etwas grundlegendem wie den Platzhaltern für Werte enttäuscht. Oder welche SQL-Typen wie auf Python-Datentypen abgebildet werden. Das die SQLite-Anbindung beispielsweise in den Grundeinstellungen nur TIMESTAMP auf `datetime`-Objekte abbildet, DATE und DATETIME aber nicht. Das ist einfach nur ärgerlich, weil man dann letztendlich doch wieder nur gegen ein spezielles DBMS programmiert, oder sich über die Abstraktionsschicht von PEP249 noch mal eine eigene stricken muss, die diese Unterschiede ausbügelt. Und bevor ich so etwas selber mache, nehme ich lieber etwas gut getestetes, fertiges.
Das Problem mit der DB-API 2.0 kenne ich 'noch' nicht und kann es daher auch nicht nachvollziehen, aber ich verstehe Dein Argument -> heisst also SQLAlchemy sollte auf dem Zettel stehen.
BlackJack hat geschrieben:Ich gehe davon aus wie man etwas lernt. Und wie man etwas nicht lernt. Du kannst ein Entwurfsmuster nur lernen wenn damit auch tatsächlich ein Problem gelöst wird, denn nur dann kann man am Ende überprüfen ob man es richtig gemacht hat. Nämlich daran ob das Problem gelöst wurde oder nicht und wie gut oder schlecht es gelöst wurde. Ein Entwurfsmuster besteht aus einem Namen, einem Problem das es löst, der Beschreibung der Lösung (als Muster), und den Konsequenzen. Und wenn man das Problem nicht benennen kann und nicht über die Konsequenzen reden kann, dann hat man eine Lösung geübt von der man nicht weiss *was* sie überhaupt löst und ob sie es besser oder schlechter löst als eine andere, vielleicht einfachere Alternative. Was ziemlich sinnfrei ist.
Man kann es aber auch päpstlicher als der Papst sehen. Ich gehe da ein bisschen anders ran. Natürlich hast Du recht, wenn Du das von mir gestellte Problem als nicht besonders geeignet ansiehst, um mit einem MVC gelöst zu werden. Aber, wer sagt denn, dass es bei diesem Problem bleibt. In der Softwareentwicklung ist es doch immer wieder das gleiche. Da kommt ein Unternehmen und will ein kleines Problem gelöst haben. Kein Problem, kann man schnell zusammenhacken. Dann fällt den Verantwortlichen ein: 'Oh, da gibt es doch noch eine andere Sache, die wir gerne gelöst hätten!' und es wird weitergestrickt. Am Ende hat man ein Konglomerat an unterschiedlichen Programmen, Anwendungen etc. die keine gemeinsamen Schnittstellen haben, die nicht vereinheitlicht sind und ganz schlimm wird es, wenn plötzlich neue Programmierer beauftragt werden und noch nicht einmal eine vernünftige Doku vorfinden. Diese Herangehensweise hat Unternehmen schon viel Geld gekostet.

Also sage ich mir: Ich weiß nicht, ob aus meinem kleinen Projekt mal irgendetwas größeres wird. Im Moment ist es nur eine Spielerei. Aber sollte sich daraus doch mehr entwickeln, habe von vornherein eine Struktur angelegt, die mir dabei hilft.
BlackJack hat geschrieben:Was ein Entwurfsmuster ist und was nicht hängt auch von der Programmiersprache ab.
Mit welchen Entwurfsmustern sollte man sich denn Deiner Meinung nach als Python-Entwickler beschäftigen? Singleton nehme ich an, Factory, Facade, Proxy?
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wobei die Klassen aus dem collections Modul durchaus eigene Basisfunktionalität implementieren. Sie sind also bei Vererbung mehr als bloß eine Art Markierung. Auch finde ich das Testen auf einen abstrakten Typen keine schlechte Idee. Man testet dann eben auf ein Protokoll, welches durch die abstrakte Klasse repräsentiert wird. Das ist zwar kein Duck Typing, aber manchmal hat man gute Gründe, zumindest grob die Art des Objekts zu ermitteln.
BlackJack

@the_real_noob: Designfragen haben auch etwas mit der Programmiersprache zu tun. Zu den gängigen „Java rules“ gehört es gegen Interfaces und nicht gegen konkrete Klassen zu programmieren. Jemand der das verinnerlicht hat, könnte ja auf die Idee kommen, dass es in Python keine Interfaces gibt, und er deswegen das nächstbeste, nämlich abstrakte Basisklassen, als Interfaces missbraucht.

Was Du beschrieben hast — abstrakte Klassen mit Funktionssammlungen — ist keine Frage von Vorlieben, das ist aus OOP-Sicht einfach falsch.

Abstrakte Klassen vorgeben ist aus meiner Erfahrung nicht die richtige Reihenfolge, beziehungsweise geht nur wenn man vorher ganz genau weiss was die abgeleiteten Klassen an Gemeinsamkeiten brauchen. Ich gehe da immer umgekehrt vor und implementiere erst die eigentlich wichtigen Klassen. Und da merkt man dann ja wenn man anfangen müsste Code oder Daten zu schreiben, die man in einer verwandten Klasse schon einmal mal geschrieben hat. *Das* ist der Zeitpunkt wo ich die ”abstrakte” Klasse erstelle, Code und Daten aus der bereits bestehenden Klasse dort hin verschiebe und dann die alte und die neue Klassen davon ableite. ”Abstrakt” in Anführungszeichen weil das in Python ja keinen wirklichen Unterschied macht. Es sei denn man macht sich noch die Mühe die auch noch mit `ABCMeta` zu markieren. Wo ich nicht wirklich einen Sinn drin sehe. Das es sich um eine Klasse handelt die für sich alleine keinen Sinn macht sieht man entweder schon an der Klasse, aber spätestens an der Dokumentation. `ABCMeta` würde dann nur noch mal die Dokumentation wiederholen, und eventuell denen auf die Finger hauen die trotzdem Exemplare erstellen wollen.

Das IMHO einzig interessante an `ABCMeta` ist, dass man damit `isinstance()` und `issubclass()` ”überschreiben” kann. Und das wird dann interessant wenn irgendwelcher Code den man nicht beeinflussen kann, zu strikte Typprüfungen macht, und man einen „duck type“ hat, der zwar die Kriterien erfüllt, wegen eines konkreten Typtests aber abgewiesen wird.

Letztlich kann man abstrakte Klassen gar nicht inflationär einsetzen sondern nur richtig oder falsch. Entweder man braucht die Klasse, dann ist sie richtig, oder man braucht sie nicht, dann ist sie falsch. Das ist eigentlich ein relativ einfacher Test.

Was päpstlicher als der Papst in diesem Kontext bedeuten soll verstehe ich nicht‽

Du implementierst ”MVC” ohne überprüfen zu können ob das tatsächlich ein Problem löst, in der Hoffnung, dass das wenn es jetzt schon kein Problem löst, dann vielleicht später eines lösen könnte wenn man das Programm erweitert. Wie willst Du das denn überprüfen? Einfach irgendwelchen unnützen Code schreiben, in der vagen Hoffnung das würde „automagisch“ irgendein zukünftiges Problem lösen, welches man gar nicht benennen kann, ist Voodoo. So etwas hat Unternehmen schon viel Geld gekostet, und andere Programmierer, die mit solchem Code arbeiten mussten, viel Nerven.

Das Gegenteil von blindlings irgendwelche Muster zu implementieren ist nicht etwas schnell zusammen hacken. Sondern sich Gedanken zu machen welche Probleme konkret gelöst werden müssen, und die so einfach und klar wie möglich zu lösen, damit eine möglichst wenig fehleranfällige, und für andere Programmierer leicht verständliche Lösung entsteht. Wenn man unnötigerweise MVC auf alle GUI-Komponenten klatscht, hat man am Ende einen Haufen unnötiger Boilerplate-Klassen die nichts zum Programm beitragen, ausser das sie Angriffsfläche für Fehler bieten, Programmierer verwirren die sich fragen was diese Klassen eigentlich sollen, und das Programm durch zusätzliche Indirektionen und zwischengeschaltete Klassen schwerer nachvollziehbar machen.

Und wie gesagt ist es ja jetzt bereits falsch implementiert, selbst wenn man mal davon absieht das gerade ”MVC” allgemein gar nicht implementierbar ist. Ich würde sogar soweit gehen und sagen das ist kein Muster. Ich kenne nämlich kein Muster bei dem man mehrere Programmierer dransetzen kann, die alle etwas anderes darunter verstehen. Der Sinn von Mustern ist doch gerade, dass jeder der den Namen hört eine mindestens sehr ähnliche Vorstellung davon hat was das bedeutet. MVC ist das schlechteste ”Muster” mit dem man sich dem Thema Entwurfsmuster nähern kann.

Die Wiederverwendbarkeit von Code ist überbewertet. Solange man keine Bibliotheken implementiert, wird der meiste Code überhaupt nicht wiederverwendet. Der meist wiederverwendete Code steckt üblicherweise schon in Bibliotheken. Von anderen. Die man dann einfach verwendet. Eigenen Anwendungscode also auf maximale Flexibilität und Wiederverwendbarkeit zu trimmen kostet unnötig Zeit und Geld. Und gerade wenn man dann mit anderer Software zusammenarbeiten muss, oder sich Anforderungen ändern, muss man oftmals viel der Anwendung umschreiben oder umstrukturieren, auch wenn man alles schön flexibel gehalten hat. Was dann zur Folge hat, dass man viel mehr Code als nötig umschreiben muss. Was mehr Zeit (und damit Geld) kostet und fehleranfälliger ist, je mehr Indirektionen und Entkopplungen man da auf ”Vorrat” mal eingebaut hat.

MVC ist kein Mittel um allgemein grafische Anwendungen zu strukturieren. Das scheint eine falsche Vorstellung bei Dir zu sein. Werd die möglichst schnell los.

Ich würde sagen man muss erst einmal die Sprache lernen bevor man sich Entwurfsmuster anschaut. Denn nur wenn man die Sprachmittel und Idiome gut beherrscht, kann man sinnvoll beurteilen ob ein Entwurfsmuster Sinn macht, oder ob es bereits ein Sprachmittel oder Idiom gibt, welches das Muster abdeckt.

Wenn man die Sprache kann, macht es Sinn sich mit allen Entwurfsmustern zu beschäftigen, um sie darauf abzuklopfen wie die Probleme, die sie lösen in der Sprache gelöst werden können, und Beispiele für die Anwendung zu finden. Denn das gehört zu Mustern auch immer dazu: reale Beispiele. Wenn man für ein Muster X kein reales Beispiel in Sprache Y findet, dann ist das mit hoher Wahrscheinlichkeit kein Muster das in Sprache Y Sinn macht.

Die letzten beiden Absätze habe ich absichtlich ”sprachneutral” gehalten, weil das nicht nur für Python gilt.

Zu den konkreten Mustern:

Singleton: Ist generell ein wenig umstritten, da es globalen Zustand darstellt. Implementierungen in Python gehen von ziemlich ”magisch” mittels `__new__()`, über das etwas weniger magische Borg-Pattern, bis hin zu gar nicht: Man erstellt einfach nur ein Exemplar und gut ist.

Factory: Die „Abstract Factory“ verschwindet in Python wegen „duck typing“ und das alles ein Objekt ist, würde ich sagen. Die „Factory Method“ ebenfalls. Das kann jedes beliebige aufrufbare Objekt übernehmen, inklusive der Klasse selbst.

Facade: Kann Sinn machen. Wobei man daran denken sollte, dass es in Python auch Funktionen gibt, man also nicht für alles Klassen verwenden muss. Und dann kann das Muster auch ”trivial” werden, eben zu einer Funktion.

Proxy: Kann man sicherlich sinnvoll einsetzen.

Noch mal zur Vorgehensweise: Entwurfsmuster spielen beim Entwurf (oder beim Refactoring) eine Rolle. Man muss den Entwurf aber schon haben. Da kann man dann entweder Muster entdecken. Oder Probleme die man durch Anwendung von Mustern lösen kann. Man geht nicht hin und fängt einen Entwurf mit Mustern an und schaut dann mal wie man das Programm da rein presst. Das wäre als wenn man von vornherein sagt, man verwendet ``for``-Schleifen und schreibt dann so etwas, mit der Begründung dass das ja irgendwann einmal mehr als ein Element werden könnte:

Code: Alles auswählen

for item in [thing]:
    frobnicate(item)
Das ist einfach nur sinnlos, und jeder andere Programmierer der das liest wird sich denken was zur Hölle sich der Programmierer dabei gedacht hat. Und das selbstverständlich rauswerfen und ein einfaches ``frobnicate(thing)`` daraus machen.

Genauso werden Muster die kein konkretes Problem lösen, beim Refactoring rausfliegen. Nettes Zitat in dem Zusammenhang: “A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.” — Antoine de Saint-Exupéry
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

@BlackJack

Hab erst einmal Dank für Deine ausführliche Antwort. Ich habe das Gefühl, mitten in ein Wespennest gestochen zu haben bzw. in einen philosophischen Diskurs geraten zu sein, wie man Software entwickelt. Hierbei scheinst Du und das meine ich Deinen Ausführungen entnehmen zu können, ein Befürworter des unkonventionellen Weges zu sein - hierfür spricht u. a. Deine Vorliebe für Python und Deine 'Ablehnung' von Java.

Selbstverständlich hast Du damit recht, wenn Du ausführst, dass man erst einmal die Software entwerfen sollte, bevor man ein bestimmtes Muster implementiert. Hier kommt der Grund ins Spiel, warum ich mich mit dem MVC beschäftige. Ich habe eine Idee für eine Anwendung und möchte diese auf verschiedenen Plattformen implementieren und dazu eine Webanwendung zur Verfügung stellen, über welche auf die volle Funktionalität der Anwendung zugegriffen werden kann.

Um das zu konkretisieren. Ein User hier im Forum hat irgendwo geschrieben, dass es in der Zukunft nur noch webbasierte Anwendungen geben wird. Ich habe daran so meine Zweifel. Allein der Ausfall der Amazon-Server vor ein paar Tagen, ist ein Grund. Aber auch die ständigen Ausfälle bei den Providern oder die immer noch sehr langsamen Verbindungen auf dem Land und insbesondere in unerschlossenen Regionen, sind m.M.n. Grund genug dafür, den Usern eine webunabhängige Anwendung zur Verfügung zu stellen, die zumindest in ihren grundsätzlichen Funktionen auch dann voll einsatzfähig ist, wenn es keine oder nur eine schlechte Internetverbindung gibt oder diese sehr teuer ist.

Somit sind schon einmal mindestens zwei verschiedene GUIs zu implementieren. Eine für das Web, eine für die Plattformen. Und dabei gibt es viele Ansichten für die unterschiedlichen Funktionalitäten.

Und rein aus Gründen der Übersicht finde ich deswegen das MVC interessant und wollte bzw. will mich mit dem oben geposteten Beispiel damit vertraut machen. Ob das nun besonders geglückt ist, sei einmal dahingestellt.

Was die Verwendung von Frameworks und Bibliotheken angeht, bin ich ein gebranntes Kind. Da kann man sich nämlich herzlich die Zähne dran ausbeißen. Entweder die angebotenen Module tun oft nicht genau das, was man will und man ist zu so großen Anpassungsarbeiten gezwungen, dass man am besten gleich selbst ein Modul erstellt oder aber, sie zwingen einem ihr eigenes Designparadigma auf.

Für letzteres ist Qt ein Beispiel. Ich will eine QEditLine mit einer Auto-Vervollständigung ausstatten. Qt bietet dafür unter anderem das Modul Completer an. Das tut aber nicht genau das, was ich will. Ich will, dass der User, wenn er seinen Wohnort eingibt, nach drei Buchstaben die Orte vorgeschlagen bekommt, die mit hoher Wahrscheinlichkeit sein Wohnort sein können. Also, wenn er 'Ber' eingibt, dann sollte als erster Vorschlag Berlin kommen und nicht Beratzhausen, Berching, Berchtesgaden. Um das umzusetzen, muss ich mir etwas eigenes basteln. Funktioniert einfach nicht so, wie ich das will. Gut, dass ist eine Aufgabe für irgendwann einmal, aber da hapert es dann nämlich sehr schnell.

Auch erfordern viele dieser Bibliotheken eine erhebliche Einarbeitungszeit und weil man so viel Zeit dafür aufgewandt hat, fixiert man sich auf diese Bibliotheken, was m.M.n. den Entwickler wieder einschränkt.

Was mich zudem bewogen hat, mich mit dem MVC zu beschäftigen, ist, dass es immer wieder einmal zu Designwechseln kommt. Sieh Dir YouTube an. Da kommt es immer wieder zu Designwechseln bzw. das Design unterscheidet sich z. B. in den USA und Deutschland. Tatsächlich fängt das schon bei der Farbwahl an, die in unterschiedlichen Kulturen unterschiedliche Bedeutungen haben. Will man seine Anwendung darauf abstimmen, ohne auch ständig mit der Funktionalität in Berührung zu kommen, was bleibt einem da für eine Wahl?

Natürlich hast Du auch in dem Punkt recht, dass man sich am besten erst einmal mit den Grundlagen einer Programmiersprache auseinandersetzen sollte, aber das ist m.M.n. genauso wie mit dem erlernen einer natürlichen Sprache. Man will sie doch nicht erst sprechen, wenn man ein umfassendes Verständnis für sie erlangt hat, sondern fängt an zu radebrechen. Hauptsache, man kann sich irgendwie verständlich machen. Und genauso ist das beim Coden. Es ist zu Anfang doch gar nicht so entscheidend, ob man schönen, sauberen Code schreibt, möglichst kurz und effektiv. Am Anfang ist es entscheidend, dass das Ding funktioniert.

Zu Deinem Zitat von Saint-Exupéry. Ist das der Grund, warum die VIPs aus Film, Fernsehen, Musik bei ihren öffentlichen Auftritten immer weniger anhaben? Zumindest die weiblichen. :D
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@the_real_noob: ich glaube Du hast eine falsche Vorstellung von Frameworks allgemein und von Qt im Speziellen. Frameworks sollen Dir helfen, komplexe Dinge so einfach zu erledigen, wie das eben bei z.B. einer Oberfläche möglich ist. Eine Autovervollständigung ist von ihrer Interaktion mit dem Nutzer komplex (Tastatur-, Mauseingaben, Listendarstellung, etc.) Darauf abstrahiert Qt mit einem Completer, der einen bestimmten Anwendungsfall recht gut abdeckt, andere durch das Überschreiben weniger Methoden aber auch erlaubt. Bei komplexen Problemen gibt es zudem nicht die eine Lösung, sondern verschiedene Ansätze, die sich irgendwo zwischen »ich gebe vieles fest vor, so dass die Benutzung einfach wird«, bis »ich muß viel lernen und viel selbst machen, dafür habe ich auch die Flexibilität« reichen. Es gibt fertige Widgets, wo die Anwendung sehr einfach ist, aber sobald es kein passendes gibt, merkst Du erst, wieviel Arbeit in einem fertigen Widget steckt, weil man dann alles selbst tun muß.
the_real_noob
User
Beiträge: 20
Registriert: Donnerstag 26. Januar 2017, 09:57

@Sirius3

Ich lehne Frameworks ja nicht komplett ab und arbeite zur Zeit auch mit Qt. Ich werde mir auch SQLAlchemy ansehen.
Ich bin eben nur skeptisch. Bei Qt gehe ich davon aus, dass dahinter eine sehr gut strukturierte Bibliothek liegt, weil dahinter eben mal Nokia stand, das als Unternehmen anders strukturiert als Open Source-Produkte. Aber es erschlägt einen auch mit seiner Komplexität und braucht eine Menge Know How, um es richtig anzuwenden. Bei Open Source-Bibliotheken wächst meine Skepsis um ein vielfaches, denn es gilt der Satz: Zu viele Köche verderben den Brei!

Soll nicht heißen, dass ich Open Source-Produkte prinzipiell ablehne.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Nokia hat an Qt nicht viel mitstrukturiert, ausser der Vorgabe zur Entwicklung von QML. Qt wird seit ~25 Jahren von einem relativ festen Entwicklerteam vorangetrieben, welches früher mal als Trolltech firmierte, dann über Nokia an Digia weitergereicht wurde. Problem mit Qt ist seit langem die Monetarisierung. Die Entwickler haben zwar ein schönes Stück Software für C++ geschaffen (C++-Puristen und Mockhasser bitte überlesen), leider lässt es sich nicht so richtig an den Mann bringen. Mit Digia bzw. der Ausgründung unternehmen sie den 5. oder 6. Versuch eines tragfähigen Geschäftsmodells, welches auch die Brötchen der Entwickler finanzieren kann.
Und Deine Skepsis gegenüber Opensource-Bibliotheken kann ich nicht ganz nachvollziehen - Deine Begründung ist idR kein Problem von Opensource-Projekten. Ich kenne kein Projekt, was im Ansturm zuvieler Entwickler in zuvielen Commits erstickt. Vorherrschend ist eher ein Entwicklermangel, solange nicht eine Firma freiwillig Kontingente abstellt. Und das tun sie nur, wenn sie sich einen Benefit davon versprechen. Und da sehe ich eher ein Problem drin - die Nachhaltigkeit von (Opensource-)Projekten ist gefährdet, wenn es keine Brötchen finanziert (siehe oben).
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@the_real_noob:

Eine strenge Umsetzung von Entwurfsmustern ist mir in Python noch nicht untergekommen bzw. werden einige der empfohlenen Muster in Python so trivial, dass man sagen muss - "ja, dass hätt ich doch sowieso so oder sehr ähnlich gelöst". Ganz anders in C++ - dort werden die Muster zusammen mit Codepattern empfohlen, weil für den Ungeübten der Dschungel aus Deklarationsmöglichkeiten unübersichtlich ist (Templatisiert? Mehrfachvererbung? Virtuell? Abstrakt? Virtuell abstrakt? usw.), die statische Typisierung spätere Umbauarbeiten schwierig bis unmöglich macht und ein eingeschlagener Weg so zur Sackgasse werden kann.
MVC sehe ich weniger als konkretes Entwurfsmuster, eher als konzeptielles Architekturmuster. Konkretes fällt dabei nicht viel ab, daher gibt es zig "Versionen" von MVC. Und die Referenz aus Smalltalk kann man nicht ohne Weiteres auf andere Sprachen übertragen, da es zu sehr mit dessen damaligen Konzepten verwoben war.
Antworten