Python Design Fehler

Alles, was nicht direkt mit Python-Problemen zu tun hat. Dies ist auch der perfekte Platz für Jobangebote.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Was findet ihr sind Python Fehler die euch immer wieder nerven.
Ich fange an.

Def für die funktions deklaration. Ich vergesse das immer.
Drache
User
Beiträge: 51
Registriert: Montag 29. November 2010, 21:51
Wohnort: Berlin
Kontaktdaten:

ich hole mal popcorn....
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Verkrüppelte Lambdas sind auch sch****e.
BlackJack

@fail: Wenn man Entwurfsfehler bemängelt, dann sollte man IMHO auch sagen was besser gewesen wäre. Bei den anonymen Funktionen ist es ja zum Beispiel nicht so, dass die Sprachentwickler nicht auch über mehr als Ausdrücke nachgedacht hätten, sondern, dass es trotz jahrelanger Diskussionen noch keinen Vorschlag gab *wie* man das vernünftig in die vorhandene Syntax integrieren kann. An der Stelle bemängelst Du also nicht die ``lambda``-Syntax, sondern das Blöcke durch Einrücken gekennzeichnet werden und vielleicht noch das die Sprachentwickler auf keinen Fall von einer LLR(1)-Grammatik abrücken wollen. Letzteres ist auch der Grund warum man ``def`` bei Funktionsdefinitionen nicht einfach weglassen kann.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Naja man könnte es in etwa so umsetzen:

Code: Alles auswählen

(a,b)=>(if a>b:{return a}else:{return b})
und das mit def wäre auch nicht so schwierig, wenn es wie ein funktionsaufruf sieht und es am anfang der Zeile kein Keyword hat, ist es eine Funktionsdeklaration.
BlackJack

@fail: Da hast Du jetzt aber nicht mehr Einrücken als Blockbegrenzung. Das kommt also nicht in Frage für Python. Mach mal ein ``from __future__ import braces``. ;-)

Ausserdem hättest Du das ``lambda``-Schlüsselwort abgeschafft, was das gleiche Problem wie die Abschaffung des ``def``-Schlüsselwortes hat: Die Grammatik ist nicht mehr LLR(1). Man muss also beliebig komplexe Ausdrücke parsen bevor man weiss was das überhaupt ist und wie man es behandeln muss. So etwas macht Parser und Compiler unnötig kompliziert und damit auch fehleranfällig.

Das Beispiel ist auch ungünstig weil man das bereits problemlos als ``lambda a, b: a if a > b else b`` ausdrücken kann. Zusätzlich haben die „curly braces” schon eine Bedeutung für `dict`- und `set`-Literalen und entsprechenden „comprehensions”. Wenn Du die verwenden willst, müsstest Du auch zeigen, dass es keine Mehrdeutigkeiten damit geben kann.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

@BlackJack Du denkst also das Python perfekt ist?
BlackJack

@fail: Nein, natürlich nicht, keine allgemeine Programmiersprache ist perfekt, weil man immer irgendwo Kompromisse eingehen muss. Die Frage ist was es für Folgen hat, wenn man Entwurfsentscheidungen anders fällt, und ob das Ergebnis davon dann nicht letztendlich für mehr Probleme sorgt, Sachen deutlich verkompliziert, und man am Ende eine komplett andere Programmiersprache hätte. Es ist immer recht einfach ein Problem zu identifizieren, das man gerade ganz konkret hat und zu sagen das müsste man anders lösen, ohne zu sagen *wie* das denn gehen soll, oder wenn man einen Vorschlag macht nicht an die Folgen davon zu denken.

Einrückung statt Blockbegrenzer ist eine Entscheidung die in Python ziemlich grundlegend ist. Das kann man mögen oder auch nicht, auf jeden Fall kann man es nicht ändern. Dafür gibt es keine Mehrheit bei den Sprachentwicklern, Guido ist dagegen, und das wird sich in Zukunft wohl auch nicht ändern. Siehe ``from __future__ import braces``. :-)

Das die Grammatik LLR(1) sein soll ist auch eine ziemlich feste Entscheidung von Guido, die auch glaube ich nicht von den anderen Sprachentwicklern gross in Frage gestellt wird. Ich persönlich finde die Entscheidung auch gut und halte sie nicht für einen Entwurfsfehler. Die Syntax bleibt dadurch einfach und leichter nachvollziehbar, und man muss nicht grössere Syntaxkonstrukte bis zum Schluss lesen um zu wissen um was es sich handelt. Es reicht ein Token voraus zu schauen um entscheiden zu können welches Konstrukt man vor sich hat. Das hat dann natürlich auch Auswirkungen auf Funktionsdefinitionen und -aufrufe die man syntaktisch irgendwie auseinander halten muss. Zum Beispiel durch ein Schlüsselwort für die Definition. Finde ich besser als ein Schlüsselwort für den Aufruf. Es gibt ja auch Sprachen wo man den Aufruf mit einem Schlüsselwort, beispielsweise ``call``, schreiben muss. *Das* hätte ich für einen Entwurfsfehler gehalten.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Okay aber tail call optimisation könnte man machen da sich Python unter anderem als funktionale sprache bezeichnet
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

fail hat geschrieben:Okay aber tail call optimisation könnte man machen da sich Python unter anderem als funktionale sprache bezeichnet
Das hat allerdings gar nichts mit dem Design zu tun, sondern hängt vom verwendeten Interpreter ab.
Das Leben ist wie ein Tennisball.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Das kann man voraussetzen z.B. bei Scheme , es ist kein Scheme-Interpreter/-Compiler wenn es kein TCO unterstützt.
BlackJack

@fail: Wie willst Du in Python denn TCO allgemein unterstützen? Das sieht für mich nach einer Forderung aus ohne sich über die Folgen oder Voraussetzungen für eine Implementierung Gedanken zu machen. Das in der Sprache zu garantieren hätte gravierende Auswirkungen auf die Implementierung und eventuell sogar auf die Semantik der Sprache. Den Namen werden erst spät aufgelöst, womit bei einem Funktionsaufruf erst klar ist welche Funktion da tatsächlich hinter steckt, wenn sie auch tatsächlich aufgelöst und aufgerufen wird. Eine Python-Implementierung müsste also zur Laufzeit ständig den Aufrufstapel im Auge behalten und schauen ob eine Funktion dort rekursiv aufgerufen wird und ob das ein optimierbarer, also endrekursiver Aufruf ist. Diesen Mehraufwand hätte man bei jedem Funktionsaufruf, obwohl die Mehrzahl der Aufrufe gar nicht rekursiv ist.

Python ist keine funktionale Sprache, sondern bietet Unterstützung um im funktionalen Stil zu programmieren, wo das Sinn macht, und in den Grenzen die eine Sprache hat, die veränderbare Zustände bietet, also quasi das Gegenteil von rein funktionaler Programmierung.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

fail hat geschrieben:Okay aber tail call optimisation könnte man machen da sich Python unter anderem als funktionale sprache bezeichnet
Clojure ist ganz definitiv eine funktionale Sprache und hat auch keine TCO. TCO scheint also gar nicht mal so wichtig zu sein.

Dazu kommen dann so spannende Fragen, wie, was eigentlich passiert wenn jemand in einer endrekursiven Funktion mit `sys._getframe()` auf Frames zugreift oder wie die Tracebacks dann aussehen.
fail
User
Beiträge: 122
Registriert: Freitag 11. Januar 2013, 09:47

Ich formuliere die Frage um. Was wünscht ihr euch von Python 4
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Weniger Trollposts?
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

BlackJack hat geschrieben:@fail: TCO allgemein unterstützen? [...] Den Namen werden erst spät aufgelöst, womit bei einem Funktionsaufruf erst klar ist welche Funktion da tatsächlich hinter steckt, wenn sie auch tatsächlich aufgelöst und aufgerufen wird. Eine Python-Implementierung müsste also zur Laufzeit ständig den Aufrufstapel im Auge behalten und schauen ob eine Funktion dort rekursiv aufgerufen wird und ob das ein optimierbarer, also endrekursiver Aufruf ist. Diesen Mehraufwand hätte man bei jedem Funktionsaufruf, obwohl die Mehrzahl der Aufrufe gar nicht rekursiv ist.
Es ist doch unerheblich, ob es sich um einen rekursiven Aufruf derselben Funktion oder einen nicht-rekursiven Aufruf einer anderen Funktion handelt, und auch, wann der Name der Funktion eines Tail Calls bekannt ist. Wichtig ist doch nur, dass der aktuelle Call Frame durch den des Tail Calls ersetzt werden kann, damit der Call Stack nicht wächst.

AFAIR hat Guido zwei Argumente gebracht, warum er keine Tail Calls möchte: erstens, weil Stack Traces nicht mehr mit dem tatsächlichen Ablauf des Programms übereinstimmen würden (was man aber wohl lösen könnte), und zweitens, dass man, wenn man möchte, Tail Calls bereits jetzt via eines Trampolins selbser bauen kann, weswegen man die Sprache nicht damit belasten muss. Dazu verweise ich auf eines meiner ersten Postings hier im Forum.

Davon abgesehen, hätte ich ebenfalls gerne Tail Calls.
In specifications, Murphy's Law supersedes Ohm's.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Also ich weiß ja nicht, aber die Fälle wo ich rekursive Funktionen tatsächlich optimiert brauche sind super selten, auch in funktionalen Sprachen. Eben weil es mächtige Funktionen wie ``map``, ``filter``, ``reduce`` und Co. gibt, die es gar nicht notwenig machen dass ich die Sachen die ich machen will überhaupt als rekursive Funktion schreiben muss.

Vom Prinzip her ganz ähnlich wie PostScript vs. Joy/Factor. In ersterem bearbeitet man den Stack, in zweiteren hat man Kombinatoren die den Stack von selbst bearbeiten und man sich stattdessen auf die Programmlogik konzentrieren kann.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
lunar

@pillmuncher Das ist nicht unerheblich. Wenn Namen so wie in Python oder Java spät aufgelöst werden, kann der Interpreter nicht statisch entscheiden, welche Funktion rekursiv aufgerufen wird. Mithin kann er den Funktionsaufruf selbst nicht einfach wegoptimieren, sondern muss bei jedem Aufruf dynamisch prüfen, ob der Stapelrahmen wiederverwendet werden kann.

Nun ist die Mehrzahl der Aufrufe aber nicht rekursiv. Die meisten Aufrufketten enden vielleicht so nach maximal 50 Aufrufen. Ich sehe nicht, warum man alle Funktionsaufrufe teurer machen sollte, nur um einen Spezialfall effizienter zu lösen.

Mag sein, dass Deine Programme besonders sind und viel Rekursion nutzen, doch Prolog-Interpreter sind nun mal kein Standardanwendungsfall. Webanwendungen dagegen brauchen keine TCO, sondern viel eher gutes Multithreading, Unicode-Handling, etc. Es gibt ganz andere, und viel wichtigere Baustellen.

Im Übrigen halte ich es auch mit Leonidas: Wenn man explizite Rekursion nutzt, ist das auch und gerade in funktionalen Sprachen eigentlich kein gutes Zeichen.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@lunar: Auch in Lisp werden Namen erst spät aufgelöst und trotzdem gibt es dort TCO (in Scheme verpflichtend). Es scheint also ein lösbares Problem zu sein, das sogar Vorteile bringt (wenigstens in Lisp), sonst wäre es wohl nicht implementiert worden. Der Stack Frame würde in Python wohl auch gar nicht wiederverwendet, sondern es würde ein neuer Frame angelegt, ein paar Zeiger verbogen, und der aktuelle Frame dem Garbage Collector überlassen werden.

Ob ein Call ein Tail Call ist, ist mehr oder weniger eine syntaktische Frage. Ist das letzte Statement in einer Funktion ein Funktionsaufruf, ist es ein Tail Call. Ist das letzte Statement in einer Funktion ein if-Statement, und ist das letzte Statement in einem Branch ein Funktionsaufruf, ist es ein Tail Call. Und so weiter. Ich vermute, der Compiler könnte schlau genug :wink: gemacht werden, sowas zu erkennen. Und das unabhängig davon, wann der Name der aufgerufenen Funktion bekannt ist, denn ob ein Statement als Funktionsaufruf erkennbar ist, ist nicht davon abhängig, wie die Funktion heißt. Ob das zu signifikanten Performance-Einbußen führen würde, könnte man AFAICS nur durch Austesten einer tatsächlichen Implementierung feststellen.

Hier sind übrigens Guidos Überlegungen zum Thema:

http://neopythonic.blogspot.de/2009/04/ ... ation.html
http://neopythonic.blogspot.de/2009/04/ ... calls.html

Zwar hätte ich gerne TCO, aber ich verliere darüber keinen Schlaf. Wenn ich wählen müsste zwischen TCO oder der Abschaffung des GILs, würde ich letzteres wählen.
In specifications, Murphy's Law supersedes Ohm's.
lunar

@pillmuncher Was letzte Aufrufe sind, weiß ich, und mir ist auch durchaus klar, dass man diese aus dem AST ableiten kann. In Python zwar etwas schwieriger als in C, Haskell, oder ML, da man die Semantik einiger Schlüsselwörter kennen muss (die Aufrufe "with foo: bar()" oder "try: foo(); finally: x()" sind nicht letzt!), aber möglich.

Scheme ist die einzige Lisp-Implementierung, die TCO erzwingt. Ich kenne Scheme nicht gut, glaube aber, dass Namen dort früh aufgelöst werden, und endrekursive Funktionen mithin in Sprünge oder Schleifen auskompiliert werden.

CL-Implementierungen, die TCO unterstützen, tun dies dagegen ziemlich sicher auf die genannte Weise: Sie werfen zur Laufzeit den Stapelrahmen weg, wenn ein letzter Aufruf stattfindet. Der teure Funktionsaufruf selbst bleibt also, womit ein großer Vorteil der TCO in Haskell oder C verloren geht. Der einzige verbleibende Vorteil der TCO ist dann, dass der Aufrufstapel nicht wächst.

Ich kann gut verstehen, dass man für diesen, in einer Sprache, in der Rekursion nicht zum normalen Idiom gehört, verhältnismäßig kleinen Vorteil nicht den Interpreter verkomplizieren und lesbare Stacktraces zerstören möchte.

Edit: Top-Level Definitionen sind in Scheme offenbar statisch, und können zur Laufzeit nicht mehr verändert werden. Mithin kann der Compiler jeden Funktionsaufruf lexikalisch auflösen und mithin statisch binden.
Antworten