Fehlersuche

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.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Sirius3 hat geschrieben:@NoPy: eine Funktion sollte eine Sache machen, und nur eine. Deine Funktionen sind mach_alles-Funktionen. Niemand baut eine Maschine, die Kaffee kochen, Auto wachen, Radio spielen und Zimmer aufräumen kann. Das Problem, das Du beschreibst gibt es also nicht, wenn man sich an die Grundsätze guter Programmierung hält, die quasi für jede Programmiersprache gelten. In C würde man auch nicht alles als void* übergeben und dann raten, auf was denn der Pointer zeigt. Dort geht es nicht, weil es gar kein Mittel zur Introspektion gibt. Warum sollte man das also in Python machen wollen?
Nein, aus Sicht eines Raumschiffes ist es eine elementare Funktion, das Zimmer sauberzubekommen. Und je nachdem, wie vielfältig die verschiedenen eingebauten Objekte sind, muss in irgendeiner Weise darauf reagiert werden. Und es kann ja nicht Sinn der Sache sein, alle Softwarebestandteile nachzubauen oder mit Wrappern zu versehen.

Um das noch ein wenig auszuschmücken:

Code: Alles auswählen

class MyMannschaftsUnterkunft(MannschaftsUnterkunft):
    def __init__(self,???):
        #wie ein Konstruktor aussehen müsste, der zuerst den geerbten Konstruktor ausführt, 
        #weiß ich im Moment nicht
        pass
        
    def SchmutzBeseitigen(self):
        #ist nur dafür da, damit Raumschiff auch mit Mannschaftsunterkunft leicht spielen kann
        self.Fegen()

class Raumschiff(objekt):
    def SchmutzBeseitigen(self,Zimmer):
        if hatFunktion('Fegen'):
            Zimmer.Fegen(heute)
        elif hatFunktion('Wischen'):
            Zimmer.Wischen(heute)
        elif hatFunktion('Sprengen'):
            Zimmer.Sprengen(heute)
        else:
            return 'Schmutz nicht entfernbar'
        
    def UeberallSchmutzBeseitigen(self):
        for Zimmer in self.AlleZimmer:
            self.SchmutzBeseitigen(Zimmer)
        self.Huelle.SchmutzBeseitigen()

    def UeberallSchmutzBeseitigen2(self):
        for Zimmer in self.AlleZimmer:
            Zimmer.SchmutzBeseitigen()
        self.Huelle.SchmutzBeseitigen()
        
    def EinfuegenZimmer(self, Zimmer):
        self.AlleZimmer.append(Zimmer) 

class Universum(objekt):
    def EinfuegenZimmerInRaumschiff(self):
        if random(3)==0:
            self.Raumschiff.EinfuegenZimmer(MyMannschaftsUnterkunft())
Nur mal so als PseudoCode. Es wäre schön, wenn ihr mir sagen könntet, wie ihr das machen würdet. Was daran falsch ist, ist leider nicht immer hilfreich.
BlackJack

@NoPy: Doch die Funktion ist überflüssig, denn das was Du da mit dem Raumschiff beschreibst ist genau das was man *nicht* machen will, weil das unflexibel und schlecht erweiterbar ist. Zimmer müssen die Methode `schmutz_beseitigen()` haben weil das Teil der API ist. Ohne diese Methode sind es für ein Raumschiff ganz einfach keine geeigneten Zimmer. Eine ``if``/``elif``-Kaskade die letztendlich auf Duck-Typen prüft ist in OOP genauso ein „anti pattern“ wie das prüfen auf konkrete Typen.

Du musst nicht herausfinden was Deine universelle Methode bekommen könnte sondern *festlegen* was sie akzeptiert und was nicht. Wenn dokumentiert ist das ein Zimmer für diese Funktion nur dann ein sinnvolles Zimmer ist wenn es eine `schmutz_beseitigen()`-Methode hat, dann ist alles geregelt. Und wirklich universelle Funktionen sind auch eher sehr selten. Damit würde ich auch nicht anfangen, denn allzu oft hat man dann einen Haufen ”universellen” Kram der genau *eine* Sache macht, aber komplizierter als wenn man vom konkreten ausgegangen wäre und dort dann die Gemeinsamkeiten abstrahiert hätte.

Bei dem Raumschiff-Beispiel würde ich ganz einfach festlegen das Zimmer eine `schmutz_beseitigen()`-Methode haben müssen und gut ist.

Wenn die das nicht bieten musst Du vielleicht Wrapper schreiben, aber der Code müsste sonst ja in der `Raumschiff.schmutz_beseitigen()` mit den vielen ``elif``\s stehen. Nur das er *dort* nicht hingehört weil das ein Albtraum beim warten und erweitern ist wenn man immer irgendwo so eine zentrale Methode hat die alles mögliche über Objekte wissen muss über die sie nichts wissen müssen sollte. Jemand der neue Zimmer baut müsste zum Beispiel Code beim Raumschiff ändern wenn er das hinzufügen möchte. Das schliesst Zimmer von Drittanbietern aus.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Die Praxis ist doch niemals so, dass Du Herr über das komplette Geschehen bist. Du bekommst Module geliefert, die zwar funktional das haben, was Du benötigst, aber nicht so, wie Du es benötigst. Selbst, wenn Du ein komplettes Projekt aus dem Nichts erschaffst, benutzt Du doch in der Regel IMMER irgendwelche Module, die auf Datenbanken, Webservices, WebFeatureServices etc. zugreifen. Und wenn Du etwas brauchst, das nach oben hin eine Funktionalität zur Verfügung stellst, die "transparent" machen, was Du benutzt, dann muss an irgend einer Stelle eine Anpassung stattfinden zwischen den verschiedenen Interfaces.

Also: Wrapper ist aus Deiner Sicht die Antwort.

Im Grunde muss man dann für jedes benutzte Modul, was nicht von sich aus in der Weise liefert, wie Du es oben benutzen willst, einen Wrapper schreiben.
Und es steht damit im Grunde fest, dass für das Modul, was ich gerade schreibe, auch wieder irgend jemand einen Wrapper schreiben muss, denn wenn aus meiner Sicht "fegen" eine sinnvolle Methode ist, ein anderer aber das universellere "SauberMachen" benutzen will, dann muss er meine Klasse kapseln.

Wie geht das unter Python richtig?

Code: Alles auswählen

class A(objekt):
    def __init__(self,a,b,c,d):
        self.x=[a,b,c,d]
    def Fegen(self):
        pass
    
class WrapperA(???):
    ???
    def SauberMachen(self):
        self.Fegen()
        
MeinA = WrapperA(1,2,3,4,5,6,7,8)
BlackJack

@NoPy: In der Praxis benutze ich die API von den Bibliotheken die ich einbinde und versuche da nicht selber vorher und prophylaktisch irgendeine universelle API als Zwischenschicht einzuziehen. Das wird doch erst interessant wenn man die Bibliothek tatsächlich mal austauschbar machen will, was in der überwiegenden Anzahl der Fälle nie passiert. Und falls es dann doch sein muss kann man zu dem Zeitpunkt noch eine eine Zwischenschicht ziehen, die dann auch tatsächlich praktisch verwendet wird. Ich denke Du gehst zu theoretisch an die Sache.

Das Problem welches Du beschreibst ist in dem Sinne dann ja auch gar nichts Python-spezifisches. Das hätte man doch in jeder Programmiersprache wenn man externe APIs benutzt die man unbedingt alle austauschbar machen möchte.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

BlackJack hat geschrieben:@NoPy: In der Praxis benutze ich die API von den Bibliotheken die ich einbinde und versuche da nicht selber vorher und prophylaktisch irgendeine universelle API als Zwischenschicht einzuziehen. Das wird doch erst interessant wenn man die Bibliothek tatsächlich mal austauschbar machen will, was in der überwiegenden Anzahl der Fälle nie passiert.
Mein Problem ist, dass ich momentan wie ein Idiot jedesmal von vorn anfange, weil ich zu unterschiedlichen Zeiten mit python zwar ähnliche Dinge mache, die aber nie vollständig identisch sind. Und es ist mir nicht gelungen, irgendetwas wiederzuverwenden, nicht einmal Copy/Paste hilft mir weiter, da mich kein Compiler vor irgendwelchen Fehlern warnt und die erst zur Laufzeit auftreten.

Ich benutze Python derzeit an 3 verschiedenen Stellen: FME (Feature Manipulating Engine), für diverse kleinere Tools, Anzapfen der Google- Maps- API (und an dieser Stelle habe ich auch den Austausch der Bibliotheken, da tatsächlich die Bibliotheken mehrerer Anbieter in Frage kommen könnten. Letztlich will ich von oben sagen: Hier hast Du 2 Adressen, berechne mir die Entfernung, die der Routenplaner liefert und die Zeit und wirf sie mir zurück. Eventuell muss das noch parametrisiert werden.

Ich habe grundsätzlich mit Datenbanken zu tun, mit Excel- Tabellen, mit Shapes etc.
Im Moment muss ich aus einer Excel- Tabelle lesen, die Abfragen an die API basteln, das Ergebnis wieder nach Excel schaufeln.

Es nervt mich total, dass ich jetzt wieder eine eigene Klasse baue für Excel- Zugriff, für Google- API etc, obwohl ich auch vorher schon mal was für Excel und diese API geschrieben habe.
Unter C; Delphi; Lazarus war das kein Problem, ich konnte auf einmal geschriebenes aufbauen.
Mir ist schon klar, dass das unter python auch gehen muss. Aber ICH weiß noch nicht, wie ICH das anfangen muss.
BlackJack hat geschrieben:Und falls es dann doch sein muss kann man zu dem Zeitpunkt noch eine eine Zwischenschicht ziehen, die dann auch tatsächlich praktisch verwendet wird. Ich denke Du gehst zu theoretisch an die Sache.
Was soll ich sagen, ehe ich es theoretisch nicht durchstiegen habe, kann ich nicht zu praktikablen Lösungen finden.
BlackJack hat geschrieben:Das Problem welches Du beschreibst ist in dem Sinne dann ja auch gar nichts Python-spezifisches. Das hätte man doch in jeder Programmiersprache wenn man externe APIs benutzt die man unbedingt alle austauschbar machen möchte.
Das hat man, aber damit komme ich schon zurecht. Das löst sich relativ einfach dadurch, dass nicht das Schriftbild des Quelltextes reglementiert ist, sondern die Schnittstelle.
Wenn ich schreibe

Code: Alles auswählen

      if (
a=1000) then
                    write('Ätsch');
a.HastDuNichtGesehen(12345);
dann sieht das zwar Scheiße aus, aber funktioniert trotzdem ohne weitere Verwirrung.
Wenn a aber mit 1000 nichts anfangen kann, weil es vom Typ string ist, dann sagt mir das der Compiler schon zur Designzeit. Dazu kommt, dass - wenn a ein Objekt ist - die IDE mir mühelos sagen kann, welche Methoden von a überhaupt an dieser Stelle zur Verfügung stehen. Und bei a.HastDuNichtGesehen kommt beim Compilieren der Fehler, nicht später. So kommt man - vorausgesetzt, jemand hat wirklich sprechende Namen verwendet - auch mit schlecht dokumentierten Modulen zurecht, auch ohne im Quelltext zu wühlen.

Das mag nicht besser sein, aber im Moment empfinde ich es als störunanfälliger. Mein eigentliches Ziel ist aber, zu lernen, wie man es mit Python SINNVOLL im Python- Stil macht, bislang kann es nicht sinnvoll gewesen sein, soviel Code, wie ich mehrfach geschrieben habe.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@NoPy: wenn Du Deinen eigenen Code nicht mehr verwenden kannst, dann ist dort das Problem zu suchen. Was Du beschreibst ist nicht programmieren, sondern raten. In der Art: ich habe hier 5 Parameter und der Compiler sagt mir schon, welche ich in welcher Reihenfolge der Funktion übergeben muß, weil sonst passt es ja vom Typ her nicht. Das kann nicht funktionieren. Typfehler sind nämlich das seltenste, was man an Fehlern macht. Die meisten kann kein Compiler entdecken, denen kann man nur auf die Spur kommen, wenn man strukturiert programmiert und weiß was man macht.
BlackJack

@NoPy: Man testet Code ja, da fallen eigentlich die Probleme die ein Compiler bei statisch typisierten Programmiersprachen findet, auf und noch mehr, weswegen ja auch bei solchen Sprachen getestet wird.

Wenn Du zwei ähnliche Dinge gelöst hast und absehbar ist das diese Lösungen so oder so ähnlich auch für das nächste Projekt sinnvoll sind, *dann* würde ich hingehen und den vorhandenen Code so umschreiben, dass eine gemeinsam benutzbare Bibliothek entsteht. Halt das was an Gemeinsamkeiten vorhanden ist, aus dem Code herausziehen. Oder wenn man etwas neues anfängt und feststellt das man etwas ähnliches schon mal gemacht hat, kann man den Zeitpunkt nutzen den entsprechenden Code aus dem alten Projekt als Bibliothek aufzubereiten.

Die Schnittstellen sind bei Python doch auch ”reglementiert”, nur mehr durch Dokumentation und nicht durch statische Typisierung, was ja auch nur ein Teil der Schnittstelle ist. Wichtig ist ja auch und gerade das Verhalten der Objekte.

Bei der Routenplaner-Geschichte würde ich eine Schnittstelle für den Routenplaner definieren, also beispielsweise das so ein Objekt eine `address_distance()`-Methode haben muss die zwei Adressen als Argumente bekommt und eine Route mit Attributen wie Entfernung und Zeit zurück liefert. Dann schreibt man für jeden konkreten Routenplaner eine Klasse die diese Schnittstelle bietet. Das würde ich in jeder objektorientierten Programmiersprache so angehen, nicht nur in Python.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Sirius3 hat geschrieben:@NoPy: wenn Du Deinen eigenen Code nicht mehr verwenden kannst, dann ist dort das Problem zu suchen.

genau, deswegen frage ich ja, wie man es richtig macht.
Sirius3 hat geschrieben:Was Du beschreibst ist nicht programmieren, sondern raten. In der Art: ich habe hier 5 Parameter und der Compiler sagt mir schon, welche ich in welcher Reihenfolge der Funktion übergeben muß, weil sonst passt es ja vom Typ her nicht. Das kann nicht funktionieren.

Doch, genau so. Dafür ist der Compiler ja da. Ich weiß bei einer Methode, die "NäheZusammen" heißt, schon, dass ich eine Nadel, ein Muster, vielleicht eine Geschwindigkeit und eine Fadenstärke übergeben muss, warum sollte ich mir die Reihenfolge der Parameter oder den Typ der selben merken, wenn der Compiler das viel besser kann?
Sirius3 hat geschrieben:Typfehler sind nämlich das seltenste, was man an Fehlern macht. Die meisten kann kein Compiler entdecken, denen kann man nur auf die Spur kommen, wenn man strukturiert programmiert und weiß was man macht.

Dann muss ich fürchterlich genial sein, denn solche Fehler, wie Du sie beschreibst, mache ich vielleicht einmal im Jahr. Den Compiler meine Tipp- und Typfehler suchen lasse ich hingegen manchmal im Minutentakt. Und die IDE die verfügbaren Methoden suchen lasse ich im Grunde bei jedem dritten Wort.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

BlackJack hat geschrieben:@NoPy: Man testet Code ja, da fallen eigentlich die Probleme die ein Compiler bei statisch typisierten Programmiersprachen findet, auf und noch mehr, weswegen ja auch bei solchen Sprachen getestet wird.
Mein Problem ist nicht so sehr das Testen, auch wenn mich untypisierte Sprachen stärker zwingen, Testklassen/Methoden/Module zu schreiben, als typisierte, weil das Arsenal an auftretenden Fehlern zumindest um diese bereichert worden ist, die sonst Compiler oder Linker herausfischen. Und man muss aus dem gleichen Grunde viel mehr Fälle abdecken, weil etwas, das sich wie eine Liste verhält, keine Liste sein muss und - wie bei mir ja gesehen - nach einmaligem durchiterieren verschwunden sein kann.
Mein Problem ist, dass ich es nicht hinbekomme, intuitiv bedienbare Schnittstellen zu schaffen, die ich wiederverwenden kann, ohne permanent Dokumentationen zu lesen, zu erzeugen, zu ergänzen. Wenn ein Modul fertig ist, dann soll es nach Möglichkeit fertig und wiederbenutzbar sein, für möglichst alle Zwecke, die sich im Kontext dieses Moduls befinden (Modul meint hier allgemein alles, von einer Funktion über eine Klasse, ein Modul bis zu einer Bibliothek)
Und wenn ich die verwendeten Farben eines Bildes als Set, als Liste, als Tupel, als Wörterbuch und als Klasse haben kann, dann müsste sie theoretisch auch mit all diesen Konstrukten spielen. Das wiederum heißt, ich muss irgendwie all diese Fälle unterscheiden (oder gemeinsame Schnittstellen nutzen). Und wenn die Farben als Text, als RGB, als CYMB oder sonstwie gespeichert werden, dann muss ich auch irgendwie darauf reagieren.

Und wenn eben der Mensch, der openpyxl geschrieben hat, sich gedacht hat, er müsse den Bereich als Generator zurückgeben, dann macht er das eben. Und ich muss es dann eben wissen. Und wenn es nicht in der Dokumentation zu erkennen ist, dann muss ich ausprobieren. Und dabei kann ich nur hoffen, dass sich diese Klasse dann immer gleich verhält und nicht bei kleineren Datenmengen Listen und bei größeren Datenmengen Generatoren verwendet. Im Gewissen Sinne bin ich dann nämlich der, der die Testmethoden zu diesem Modul schreibt, zumindest, soweit ich diese Funktionen benutzen will.

Daher meine Frage: Wenn ich nach oben stabil sein will und die eigentliche Geschäftslogik, die eine simple ist, so simpel halten möchte, wie genau kapsele ich dann sinnvoll all das, was sich irgendjemand mal ausgedacht hat (dieser irgendjemand kann auch ich sein) und von dem ich nur Bruchstücke weiß?
BlackJack hat geschrieben:Wenn Du zwei ähnliche Dinge gelöst hast und absehbar ist das diese Lösungen so oder so ähnlich auch für das nächste Projekt sinnvoll sind, *dann* würde ich hingehen und den vorhandenen Code so umschreiben, dass eine gemeinsam benutzbare Bibliothek entsteht.

Das ist Wiederverwendbarkeit in Deinen Augen? Ich würde das ja so angehen, dass ich die vorhandenen Module benutze und in ein weiteres spezifisches einfließen lasse. Dann muss ich mir nämlich auch keinen Kopf mehr machen, welche Auswirkungen das Redesign auf von den Ursprungsmodulen abhängigen Code hat.
BlackJack hat geschrieben:Halt das was an Gemeinsamkeiten vorhanden ist, aus dem Code herausziehen. Oder wenn man etwas neues anfängt und feststellt das man etwas ähnliches schon mal gemacht hat, kann man den Zeitpunkt nutzen den entsprechenden Code aus dem alten Projekt als Bibliothek aufzubereiten.
Allen ernstes? Du kaufst immer die ganze Kuh, wenn Du einen Becher Milch trinken willst?
BlackJack hat geschrieben:Die Schnittstellen sind bei Python doch auch ”reglementiert”, nur mehr durch Dokumentation und nicht durch statische Typisierung, was ja auch nur ein Teil der Schnittstelle ist. Wichtig ist ja auch und gerade das Verhalten der Objekte.
In meinen Augen widerspricht sich das. Wenn ich im Sinne von OOP nur atomare Funktionen mit sprechenden Namen verwende, dann ist das einzige, was ich noch beschreiben muss, die Liste der möglichen Parameter. Und "reglementiert" würde für mich bedeuten, dass ein Regelversto0 geahndet würde. Jedes meiner Python- Schnipsel war lauffähig ohne das geringste Maß an Dokumentation.
Es ist eigentlich vielmehr eine Best Practice beschrieben, als eine Regel festgelegt.
BlackJack hat geschrieben:Bei der Routenplaner-Geschichte würde ich eine Schnittstelle für den Routenplaner definieren, also beispielsweise das so ein Objekt eine `address_distance()`-Methode haben muss die zwei Adressen als Argumente bekommt und eine Route mit Attributen wie Entfernung und Zeit zurück liefert. Dann schreibt man für jeden konkreten Routenplaner eine Klasse die diese Schnittstelle bietet. Das würde ich in jeder objektorientierten Programmiersprache so angehen, nicht nur in Python.
Na klar, bei jeder anderen Programmiersprache weiß ich auch, wie ich das machen soll, nur nicht in python.

google maps API Dokumentation: https://developers.google.com/maps/docu ... ions/intro
python json Dokumentation: https://docs.python.org/2/library/json.html

Und Du kannst allein aus diesen Dokumentationen erkennen, an welchen Stellen sich die Entfernungen/Zeiten wie auslesen lassen?
https://maps.googleapis.com/maps/api/di ... n=Montreal
liefert jede Menge JSON- Match, es war nervtötendes Basteln, an welchen Stellen das als was aufgelöst wurde. Gut, das ist nun fertig, aber ich möchte es eigentlich erst wieder anfassen, wenn entweder meine Bedürfnisse sich geändert haben, oder google die Schnittstelle geändert hat.

Daher würde ich eben gern wissen, wie ich wrapper- KLassen (auch für meine eigenen) schreiben kann. Meine Frage von vorhin bleibt bestehen, gern auch ein entsprechendes Tutorial.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@NoPy: Schon deine erste Frage hier im Forum war, wie man den Datentyp einer Variablen deklarieren kann. Du scheinst immer noch nicht darüber hinweg gekommen zu sein, dass das in Python eben nicht geht. Und dass es nicht geht, ist kein Defizit der Sprache Python, sondern eine bewusste Designentscheidung, die sich mindestens bis ins Jahr 1958 zurückverfolgen lässt, als LISP erfunden wurde. Ich weiß auch nicht, wie oft ich noch wiederholen muss, dass Python eine Art Lisp mit weniger Klammern ist, kein C/C++/C#/Java mit weniger Klammern. Statt auf Compilermeldungen verlässt man sich in Python auf die Dokumentation und auf Tests und als Belohnung erhält man Code, der manchmal sogar um Größenordnungen kürzer und prägnanter ist, als in den genannten Bondage & Discipline Sprachen. Wie man damit umgeht, muss man sicherlich lernen, und das geht IMO am besten, wenn man sich den Code erfahrener Pythonistas ansieht. Große Teile der Standardlib sind in Python programmiert, weswegen das ein guter Startpunkt wäre. Außerdem sollte man PEP8 und PEP20 beherzigen.

Auf deine Frage bzgl. Listen/Tupel/Generatoren: alle diese implementieren das Iterable Protokoll, deswegen sollte man sich darauf stützen. Statt Datenstrukturen zu manipulieren, würde man Transformationen auf Daten implementieren und diese zu Pipelines zusammenfügen. Die Mittel der Wahl dazu sind List/Set/Dictionary Comprehensions und Generator Expressions.

Wenn du ein konkretes Codebeispiel hast, das die Problematik verdeutlicht, vor der du stehst, kann man vielleicht helfen.

Achja: Python ist nicht untypisiert, sondern stark dynamisch typisiert.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@NoPy: Falls Du Compiler wirklich als Werkzeuge ansiehst Dir die Argumentreihenfolge zu erklären und ihn im Minutentakt als Fehlersuche verwendest, dann sind dynamisch typisierte Programmiersprachen nichts für Dich. Denn da musst Du Dir tatsächlich merken wie die API aussieht, denn der Compiler hilft Dir da nicht. Da musst Du zwangsläufig Deine Arbeitsweise ändern. Was die Sprache nicht an statischen Informationen einfach verfügbar macht, kann eine IDE nicht berücksichtigten. Ein bisschen was geht, aber halt nicht alles.

Bist Du sicher das Du nur einmal im Jahr einen Fehler machst der nicht auf einen falschen Typ basiert, oder testest Du einfach nur nicht systematisch und stösst deshalb seltener auf diese Fehler?

Python ist übrigens nicht untypisiert sondern dynamisch und stark typisiert.

Dokumentieren ist ein gutes Stichwort. Das sollte man tun. Und aktuell halten. Sollte man allerdings auch bei statisch typisierten Programmiersprachen so machen. ”Intuitiv” benutzbar ist nämlich so eine Sache.

Das Beispiel mit den Farben, da würde ich wieder sagen dieser „ich muss schauen wie die Farben übergeben wurden“-Ansatz und dann entscheiden was ich damit mache einfach von Anfang an falsch. Man muss einfach nur Vorschreiben wie die Farben auszusehen haben, möglichst *einen* (Duck-)Typ. Man muss nicht zig Sachen unterscheiden sondern einfach *eine* Vorgeben.

Wenn Du von openpyxl einen Bereich als Liste benötigst, dann mach einfach eine Liste draus. Das funktioniert auch mit einer Liste. Ganz allgemein mit jedem iterierbaren Objekt. Das wäre dann die einzige Eingenschaft die auch in Zukunft erfüllt sein muss.

Das was ich beschrieben habe ist mein pragmatischer Ansatz zur Wiederverwendbarkeit. Ich versuche nicht alles von vornherein als universell wiederverwendbare Bibliothek zu schreiben weil ich festgestellt habe das ich in sehr vielen Fällen dabei Energie aufwände die unnötig ist weil's dann doch nicht wiederverwendet wird. Ich schreibe lieber konkrete, anwendbare Lösungen als mir irgendwelche komplexen APIs auzudenken die wenn sie dann auf die Realität treffen doch nicht 100% passen und dann auch nur einmal ein Teil davon verwendet wird. You Ain't Gonna Need It (YAGNI) ist hier das Stichwort. Sofern bestimmte Flexibilität nicht einfach und zum ”Nulltarif” (oder nahe dran) implementiert werden kann, baue ich sie nicht ein wenn ich nicht weiss oder konkret absehen kann das sie benötigt werden wird.

Das das Extrahieren oder Verändern von Bibliotheken Einfluss auf die alten, bestehenden Projekte hat sehe ich als Vorteil, denn die dienen damit mit ihren Tests und auch im Betrieb als Überprüfung der Bibliothek und sichern/erhöhen damit die Qualität. Wenn ich mir einen Kopf um die Auswirkungen eines Redesigns in Bezug auf den bereits abhängigen Code machen muss, dann muss ich mir naturgemäss auch mehr Gedanken um die API machen und dadurch entsteht dann hoffentlich eine bessere als wenn die nur für ein Projekt zugeschnitten ist. Das macht sich dann beim nächsten Projekt bezahlt das diese Bibliothek benutzt.

Das mit der Kuh und der Milch habe ich nicht verstanden. Also ich kenne die Redewendung, bekomme den Bezug zu dem Zitat aber nicht hin.

”Reglementiert” hatte ich nicht umsonst in Anführungszeichen gesetzt. Es wird halt kein Regelverstoss geahndet. Obwohl: Doch, das Programm funktioniert nicht korrekt wenn man sich nicht an die Regeln hält.

Wenn Du das vorgehen bei anderen Programmiersprachen weisst, wo liegt dann das konkrete Problem bei Python?

Was hat denn die Maps-API mit Python zu tun? Aus den JSON-Antworten herauszufinden wo da welche Information steht ist doch ein Problem das sich völlig unabhängig von der für die Anfrage verwendeten Programmiersprache stellt.

Ich sehe bei der API jetzt auch kein nervtötendes basteln, das Format ist doch auf der verlinkten Webseite recht ausführlich beschrieben.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

pillmuncher hat geschrieben:@NoPy: Schon deine erste Frage hier im Forum war, wie man den Datentyp einer Variablen deklarieren kann. Du scheinst immer noch nicht darüber hinweg gekommen zu sein, dass das in Python eben nicht geht. Und dass es nicht geht, ist kein Defizit der Sprache Python, sondern eine bewusste Designentscheidung, die sich mindestens bis ins Jahr 1958 zurückverfolgen lässt, als LISP erfunden wurde. Ich weiß auch nicht, wie oft ich noch wiederholen muss, dass Python eine Art Lisp mit weniger Klammern ist, kein C/C++/C#/Java mit weniger Klammern. Statt auf Compilermeldungen verlässt man sich in Python auf die Dokumentation und auf Tests und als Belohnung erhält man Code, der manchmal sogar um Größenordnungen kürzer und prägnanter ist, als in den genannten Bondage & Discipline Sprachen. Wie man damit umgeht, muss man sicherlich lernen, und das geht IMO am besten, wenn man sich den Code erfahrener Pythonistas ansieht. Große Teile der Standardlib sind in Python programmiert, weswegen das ein guter Startpunkt wäre. Außerdem sollte man PEP8 und PEP20 beherzigen.
Nun ja, darüber bin ich schon lange weg. Dass Programmierung in python für mich persönlich so ist, als hätte man mir einen Arm auf den Rücken gebunden und an der anderen Hand Daumen und Ringfinger zusammengeklebt, werde ich nicht an den Design- Entscheidungen nicht rütteln, wozu auch.
Das Beherzigen von PEP* ist für mich nicht sonderlich hilfreich. Im Wesentlichen stehen dort viele Dinge zur Quellcodeformatierung (was ich bislang getrost einem Code- Beautifier überlassen durfte) und ein paar seeeeeehhhhr allgemeine Ratschläge á la
"Wähle den einfachsten Weg"
"Mach es gleich"
...
Das ist für mich wirklich so hilfreich, wie ein Nuckel.
pillmuncher hat geschrieben:Auf deine Frage bzgl. Listen/Tupel/Generatoren: alle diese implementieren das Iterable Protokoll, deswegen sollte man sich darauf stützen. Statt Datenstrukturen zu manipulieren, würde man Transformationen auf Daten implementieren und diese zu Pipelines zusammenfügen. Die Mittel der Wahl dazu sind List/Set/Dictionary Comprehensions und Generator Expressions.
Ist schon klar, statt
x.tu_was_mit_ding(y)
y_neu = x.baue_neues_ding_aus_y(y)
und so weiter.
pillmuncher hat geschrieben:Wenn du ein konkretes Codebeispiel hast, das die Problematik verdeutlicht, vor der du stehst, kann man vielleicht helfen.
Weiter oben: Wie baue ich eine Wrapper- Klasse, um "fremde" oder "alte" Klassen an neue Herausforderungen anzupassen?
Wiederholung:

Code: Alles auswählen

class A(objekt):
    def __init__(self,a,b,c,d):
        self.x=[a,b,c,d]
    def Fegen(self):
        pass
    
class WrapperA(???):
    ???
    def SauberMachen(self):
        self.Fegen()
        
MeinA = WrapperA(1,2,3,4,5,6,7,8)
pillmuncher hat geschrieben:Achja: Python ist nicht untypisiert, sondern stark dynamisch typisiert.
schon klar
BlackJack

Wirklich konkret ist das abstrakte Beispiel ja nicht. Da würde ich wahrscheinlich einfach das hier machen:

Code: Alles auswählen

class A(object):

    def __init__(self, a, b, c, d):
        self.x = [a, b, c, d]

    def fegen(self):
        pass


class WrapperA(A):

    saubermachen = A.fegen
Wähle den einfachsten Weg halt. :-) (Noch einfacher wäre vielleicht ein „monkey patch“, aber auch deutlich unsauberer.)

Eine allgemeingültige Antwort kann man aber auch gar nicht geben *und* ich sehe auch immer noch nicht wie sich an dieser Stelle Python von anderen OOP-Programmiersprachen unterscheidet. Du sagst ja in anderen Sprachen wäre das kein Problem. Welche Sprachen sind das denn? Und wie würdest Du es dort machen?
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Das Problem verstehe ich immer noch nicht. Besser gesagt, ich verstehe nicht, inwiefern sich Python hier anders verhalten soll, als andere OOP-Sprachen. Wenn etwas aus Teilen besteht und eine polymorphe Operation auf diesen Teilen ausgeführt werden soll, obwohl die Operation nicht für alle Teile definiert ist, hilft einem statische Typisierung auch nicht weiter. Man muss immer einen Adapter schreiben. Oder einen Visitor, mit allen Problemen die das mit sich bringt. Oder Multimethoden. Das ist aber nichts Python-spezifisches.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

BlackJack hat geschrieben:Wirklich konkret ist das abstrakte Beispiel ja nicht. Da würde ich wahrscheinlich einfach das hier machen:

Code: Alles auswählen

class A(object):
    def __init__(self, a, b, c, d):
        self.x = [a, b, c, d]

    def fegen(self):
        pass

class WrapperA(A):

    saubermachen = A.fegen

#Das heisst also, mit 
MyA = WrapperA(1,2,3,4)
#erzeuge ich ein Objekt, bei dem 
print MyA.x
#[1,2,3,4] liefert
MyA.fegen() #ruft pass auf
MyA.saubermachen() #ruft fegen auf
Wähle den einfachsten Weg halt. :-) (Noch einfacher wäre vielleicht ein „monkey patch“, aber auch deutlich unsauberer.)
Und um den Konstruktor anzupassen, müsste also folgendes funktionieren

Code: Alles auswählen

class A(object):
    def __init__(self, a, b, c, d):
        self.x = [a, b, c, d]

class WrapperA(A):
    def __init__(self, a, b, c, d, e):
        A.__init__(a,b,c,d)
        self.x.append(e)
BlackJack hat geschrieben:Eine allgemeingültige Antwort kann man aber auch gar nicht geben *und* ich sehe auch immer noch nicht wie sich an dieser Stelle Python von anderen OOP-Programmiersprachen unterscheidet. Du sagst ja in anderen Sprachen wäre das kein Problem. Welche Sprachen sind das denn? Und wie würdest Du es dort machen?
In Pascal sähe das so aus:

Code: Alles auswählen

  { TA }
  TA = class(TObject)
    x : array of string;
    constructor Create(const a,b,c,d:string);
    procedure Fegen();
  end;

  { TWrapperA }
  TWrapperA = class(TA)
    constructor Create(const a,b,c,d,e:string);
    procedure SauberMachen();
  end;

{ TA }
constructor TA.Create(const a, b, c, d: string);
begin
  setlength(x,4);
  x[0]:=a;
  x[1]:=b;
  x[2]:=c;
  x[3]:=d;
end;

procedure TA.Fegen;
begin

end;

{ TWrapperA }
constructor TWrapperA.Create(const a, b, c, d, e: string);
begin
  inherited Create(a,b,c,d);
  setlength(x,5);
  x[4]:=e;
end;

procedure TWrapperA.SauberMachen;
begin
  Fegen()
end;
Ist natürlich länger bzw. inhaltlich unflexibler, aber dafür schreit der Compiler auch sofort laut los, wenn ich versuche, als Parameter eine Zahl zu übergeben.
Also kann ich sofort mühelos explizit konvertieren.

Aber ich denke, es ist klar geworden, dass ich nicht versuche, hier einen Glaubenskrieg der Programmiersprachen zu entfachen. Es ist eher so, dass ich versuche, dieses nicht als Konflikt zu verstehen. Ich möchte einfach lernen, effektiv mit dem Werkzeug python umzugehen. Sicher hat sich auch meine persönliche Stoßrichtung geändert, ich schreibe nicht mehr Programme für irgendwelche Nutzer, auch muss ich in der Regel nicht mehr über Multithreading und Nebenläufigkeit, GUI oder ähnliches diskutieren oder mich mit Kollegen abstimmen. Dafür schreibe ich mir jetzt meine eigenen Tools. 100 Stück pro Jahr, jedes mit eigener Zielsetzung, aber großen Überschneidungen hinsichtlich der verwendeten "Schnittstellen" (FME, Excel, Oracle, Access, QGIS, DBF ...)
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@NoPy: Ich verstehe es immer noch nicht. Einerseits möchtest du eine polymorphe Operation anwenden auf Dinge, die diese Operation nicht bereitstellen und deswegen adaptiert werden müssen. So weit, so OOP. Andererseits vermisst du die statische Typprüfung von Methodenargumenten, was mit der ersten Fragestellung gar nichts zu tun zu haben scheint. Was ist denn nun dein Problem?

Und: Python ist auch mehr LISP als Pascal. Pascal und C sind eh dieselbe Sprache.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

pillmuncher hat geschrieben:@NoPy: Ich verstehe es immer noch nicht. Einerseits möchtest du eine polymorphe Operation anwenden auf Dinge, die diese Operation nicht bereitstellen und deswegen adaptiert werden müssen. So weit, so OOP. Andererseits vermisst du die statische Typprüfung von Methodenargumenten, was mit der ersten Fragestellung gar nichts zu tun zu haben scheint. Was ist denn nun dein Problem?

Und: Python ist auch mehr LISP als Pascal. Pascal und C sind eh dieselbe Sprache.
Noch mal: Ich will keinen Glaubenskrieg anfangen über Pascal/C/LISP/Python/Prolog/Forth/Basic
Alle haben Gemeinsamkeiten und Unterschiede, Vorteile und Nachteile

Ich will schlicht lernen, wie ich am effizientesten python nutze, um meine Probleme zu lösen. Es ist mir letztlich egal, wie das oder die Konzepte heißen und funktionieren.
Ich muss dazu die Konzepte lernen - das ist mir ganz klar.
Ich muss meine Aufgaben lösen - das sollte Euch klar sein.

Ich habe einen anderen Background und vor diesem Hintergrund bin ich einiges an Features gewöhnt, die nun zwangsläufig wegfallen. Dafür gewinne ich Freiheiten und neue Features. So ist das eben.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@NoPy: Um dir helfen zu können, muss ich wissen, welches Problem du hast. Ich habe es bis jetzt nicht verstanden.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

pillmuncher hat geschrieben:@NoPy: Um dir helfen zu können, muss ich wissen, welches Problem du hast. Ich habe es bis jetzt nicht verstanden.
  • -Das ursprüngliche Thema "Fehlersuche" bezog sich darauf, dass ein von mir benutztes Modul (openpyxl) generatoren zurückgeliefert hat, so dass für mich virtuell die Elemente nach dem ersten Ansehen verschwanden ("Burn after Reading").
    -Das führte im Weiteren dazu, dass ich mich informieren wollte, wie man python- like damit umgeht, wenn die eigenen Module etwas verwenden möchten, aber nur prinzipiell und nicht genau dazu passen.
    -Die Antwort von BlackJack war letztlich: Lieber Wrapperklassen, die die verwendeten Module kapseln, als die benutzenden Klassen variabel zu gestalten.
    -Seit dem versuche ich, die Mechanismen zu lernen, wie man python- like wrapper- Klassen baut.
Eigentlich ist für mich die Frage mittlerweile beantwortet (mal abgesehen von meiner letzten Reaktion auf die Antwort von BlackJack).
Im Moment versuche ich aus den vielen Versuchen, mir die Sprache python besonders schmackhaft zu machen, mir die Krumen Information herauszupicken, mit denen ich etwas anfangen kann.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@NoPy: Zur Frage nach den Generatoren: tatsächlich betrifft das Iteratoren. Generatoren sind manche Dinge, die Iteratoren erzeugen. Der Zusammenhang ist dieser. Generatorfunktionen sind Funktionen oder Methoden, die wenigstens ein yield-Statement haben. Generatorausdrücke sehen aus wie List Comprehensions, außer dass sie in runde statt eckige Klammern eingeschlossen sind. Wenn ich eine Generatorfunktion ausführe, erhalte ich als Ergebnis einen Generator zurück. Wenn ich einen Generatorausdruck auswerte, ebenfalls. Generatoren implementieren das Iterator-Protokoll, dh., sie besitzen eine __iter__() und eine __next__() Methode (next() in Python2.x). Generatoren haben zusätzlich noch weitere Methoden: send() und close() und throw(). Objekte, die eine __iter__() Methode haben, implementieren das Iterable-Protokoll. Wir haben also eine Hierarchie: Iterable <-- Iterator <-- Generator. Das ist keine Vererbungshierarchie, denn wir arbeiten hier mit Duck-Typing, nicht mit Vererbung. Deswegen nennen wir es auch Protokoll, und nicht Basisklasse. Jedes Objekt kann jedes beliebige Protokoll implementieren, wenn der Programmierer das so festlegt (indem er es so programmiert).

Iteratoren haben also genau zwei Methoden: __iter__() und __next__(). Die erste ist dafür da, damit man auf einem Iterator it wieder iter(it) aufrufen kann. Das braucht man manchmal. it wird also von iter() als Iterable verwendet. Die zweite Methode dient zum eigentlichen iterieren. In einer for-Schleife wird sie implizit aufgerufen, aber das kann man auch explizit machen, indem man zB. elem = next(it) schreibt. Die Funktion next() könnte man, wenn es sie nicht schon gäbe, so implementieren:

Code: Alles auswählen

def next(iterator):
    return iterator.__next__()
Analog dazu ruft iter(it) einfach it.__iter__() auf, aber wenn das fehlschlägt, versucht iter() über den Subscriptionsoperator an die Elemente zu kommen. Das interessiert uns hier aber nicht.

Man verwendet Generatoren bevorzugt dann, wenn das Berechnen der Elemente sehr teuer ist oder die Elemente sehr groß sind. Oder wenn es sehr viele Elemente sind, so dass eine Liste mit allen Elementen sehr viel Speicher benötigen würde. Oder in einem Fall wie diesem: Nehmen wir an, ich wollte einen Iterator haben, der Daten aus einer Netzwerkverbindung liefert, also Daten, die irgendein anderer Computer sendet. Nehmen wir irgendein simples Protokoll an, wo nur Strings gesendet werden, und ein '\n' sei der Trenner zwischen zwei Strings und '\0' signalisiert das Ende der Daten. Nach senden von '\0' wird die Verbindung geschlossen. Ich könnte das so implementieren:

Code: Alles auswählen

class NetIter:

    def __init__(self, host, port):
        self.host = host
        self.port = port

    def __iter__(self):
        connection = some_connect_function(self.host, self.port)
        while True:
            data = ''
            while True:
                data += connection.recv()
                if data[-1] == '\n':
                    yield data[:-1]
                    break
                elif data[-1] == '\0':
                    yield data[:-1]
                    return
Iter ist hier als Generatorfunktion implementiert. Angenommen, ich würde sowas machen:

Code: Alles auswählen

netit = NetIter('localhost', 12345)
xs = list(netit)
ys = list(netit)
Würdest du da erwarten, dass xs und ys dieselben Daten enthalten? Wenn nein, warum dann bei openpyxl? Dass die Daten über das Netzwerk kommen, ist dabei übrigens völlig unerheblich. Wichtig ist dagegen, was das Iterator-Protokoll garantiert: dass ein Element nach dem anderen geliefert wird, bis es entweder keine mehr gibt oder keine mehr nachgefragt werden.

Ein völlig anderes Problem ist, was ich mit den gelieferten Elementen anstelle. Sequentiell aus allen Elementen einer Struktur eine neue, evtl. nicht-isomorphe Struktur zu erzeugen, ist ein left fold. Den kann ich mittels Rekusion erzeugen, wie du das in Dump() getan hast, oder mittels einer for-Schleife (evtl. + Stack) oder mittels reduce(). Einfach ist es, wenn alle Elemente auf dieselbe Weise behandelt werden müssen. Schwieriger, wenn man einen Dispatch durchführen muss. Der Dispatch kann durch Konditionale (if-Statements) durchgeführt werden, aber zB. auch dadurch, dass ich mir Pythons Methoden-Dispatch zunutze mache. Guido nannte das AFAIR mal das Command Dispatch Pattern:

Code: Alles auswählen

class Foo:
    pass

class Bar:
    pass

class Dispatcher:

    def dispatch_Foo(self, elem):
        print 'Foo found!', elem

    def dispatch_Bar(self, elem):
        print 'Bar found!', elem

    def dispatch(self, elem):
        getattr(self, 'dispatch_' + type(elem).__name__)(elem)

d = Dispatcher()
d.dispatch(Foo())
d.dispatch(Bar())
Das hat aber dasselbe Problem wie explizite if-Abfragen und Visitors. Man muss immer, wenn ein neuer Fall dazukommt, alle Stellen ändern, wo man auf den neuen Fall reagieren muss. Aber das ist kein Problem von Python oder dynamisch typisierten Sprachen, sondern von allen mir bekannten Sprachen. Auch statische Typisierung hilft hier nicht, denn es gibt keine automatische Typprüfung der Art "Oh, hier ist was neues, da generiere ich mal automatisch den Code, den der Programmierer haben möchte, um mit dem neuen Dings umzugehen." Und auch mit Iteratoren hat das nichts zu tun, denn für diese Problemstellung ist es wurst, ob die Daten von einem Iterator geliefert werden oder aus einer Liste oder sonstwo her stammen. Entweder die Elemente haben dasselbe polymorphe Interface, wodurch man den Dispatch geschenkt bekommt, oder man muss den Dispatch selber durchführen, mit den og. Problemen. Manchmal muss man dazu Adapter (welche du Wrapper nennst) schreiben. Auch das hat weder etwas mit dynamischem vs. statischem Typing zu tun, noch mit Iteratoren.

Schlussendlich: mir ist immer noch nicht klar, wo dein Problem liegt. Abgesehen von dem oben gezeigten Command Dispatch Pattern, das so in Bondage & Discipline Sprachen nicht implementierbar ist, hat das alles nichts mit Python zu tun, sondern nur ganz allgemein mit OOP.
In specifications, Murphy's Law supersedes Ohm's.
Antworten