@OrhanBencegay: Unterm Strich musst Du das einfach auswendig lernen, welche Funktionen es gibt, und welche Methoden die einzelnen Datentypen haben. Beziehungsweise immer nachlesen. Sachen die man häufiger braucht, lernt man dann ja automatisch auswendig.
``len(sized_thing)`` vs. ``sized_thing.len()`` ist teilweise historisch bedingt. Es gab in Python früher mal einen Unterschied zwischen „type“ und „class“. Eingebaute Typen waren etwas besonderes, konnten nur in C geschrieben werden, nicht in Python, und eingebaute Funktionen wie `len()` — die ja auch in C geschrieben sind, ”kannten” die eingebauten Typen und haben da entsprechend drauf reagiert. Dafür, dass man sie auch mit eigenen, in Python geschriebenen Klassen, verwenden kann, gibt es die speziellen ”magischen” Methoden wie `__len__()` die von `len()` aufgerufen wird, wenn die Funktion einen Wert bekommt, den sie nicht ”kennt” und besonders/effizienter behandeln kann. Und auch wenn mittlerweile „type“ und „class“ zu „class“ vereint wurden und oberflächlich alle Datentypen gleich sind, kann `len()` in CPython bei einigen eingebauten Datentypen die Länge effizienter ermitteln als über den Aufruf der `__len__()`-Methode, die diese Objekte auch haben.
Und zum anderen ist es Geschmackssache und auch teilweise Erfahrung was Leute als leichter verständlich ansehen. In diesem Fall Guido van Rossum, als er diese Entwurfsentscheidungen traf. Zitat aus einer Antwort zu genau der Frage warum ``len(sized_thing)`` statt ``sized_thing.len()`` (
https://mail.python.org/pipermail/pytho ... 04643.html):
First of all, I chose len(x) over x.len() for HCI reasons (def
__len__() came much later). There are two intertwined reasons
actually, both HCI:
(a) For some operations, prefix notation just reads better than
postfix -- prefix (and infix!) operations have a long tradition in
mathematics which likes notations where the visuals help the
mathematician thinking about a problem. Compare the easy with which we
rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness of
doing the same thing using a raw OO notation.
(b) When I read code that says len(x) I *know* that it is asking for
the length of something. This tells me two things: the result is an
integer, and the argument is some kind of container. To the contrary,
when I read x.len(), I have to already know that x is some kind of
container implementing an interface or inheriting from a class that
has a standard len(). Witness the confusion we occasionally have when
a class that is not implementing a mapping has a get() or keys()
method, or something that isn't a file has a write() method.
Saying the same thing in another way, I see 'len' as a built-in
*operation*. I'd hate to lose that. […]
Man kann sich an den Stellen wo es eine eingebaute Funktion gibt, deren verhalten letztlich über eine oder mehrere ”magische” Methoden implementiert wird, vorstellen, dass die Sprachentwickler/Guido das als eine Art Operator ansehen, so wie bei den ”magischen” Methoden, die das Verhalten von Operatoren bestimmen. `len()` ist ein Operator, aber eben ohne eigene Syntax.
Solche Entscheidungen werden oft auch von Erfahrungen beeinflusst. Also was die Leute die diese Entscheidungen treffen schon so kennen, und gewohnt sind. In zwei der Programmiersprachen die Python beeinflusst haben, ABC und Icon, gibt es beispielsweise einen Operator für die Länge. In ABC ist das ``#`` und in Icon ``*``. Folgendes Icon-Programm gibt beispielsweise 3 (Länge der Liste) und 11 (Länge der Zeichenkette) aus:
Code: Alles auswählen
procedure main()
names := ["Peter", "Paul", "Mary"]
write(*names)
write(*"Mississippi")
end
Wie kamen die Entwickler von Sprachen wo `len()` als Operator implementiert ist darauf das so zu machen? Wahrscheinlich von der Mathematik beeinflusst, wo beispielsweise ``| |`` als Operator in verschiedenen Kontexten als ”Anzahl” oder ”Länge” definiert ist. Beispielsweise in der Mengenlehre ist |A| die Anzahl der Elemente in der Menge A. Oder in der Geometrie |AB| die Länge der Strecke zwischen den Punkten A und B.
Bei der Entscheidung ``separator.join(iterable)`` vs. ``iterable.join(separator)`` ist eine Überlegung wo man die Arbeit rein steckt diese Funktionalität zu implementieren.
In Python hat der Datentyp `str` eine `join()`-Methode die beliebige iterierbare Objekte entgegen nimmt die Zeichenketten liefern. Listen, Tupel, Mengen, Dateiobjekte, und so weiter. Diese Liste von Datentypen ist potentiell unendlich, denn jeder kann selbst so einen Datentypen implemetieren, und die Leute tun das auch, oder man kann mit `map()` oder Generatorausdrücken ad hoc Objekte erstellen, die man der `str.join()`-Methode übergeben kann.
Falls es die `join()`-Methode auf Zeichenketten nicht geben würde und man den ``iterable.join(separator)`` Weg beschreiten würde, dann müsste jeder iterierbare Datentyp so eine `join()`-Methode haben. Statt nur der `str`-Datentyp. Im schlimmsten Fall müsste jeder der einen iterierbaren Datentyp erfindet `join()` immer wider selbst schreiben, obwohl das ja immer der gleiche Code ist. Oder man müsste zumindest den Code, der einmal irgendwo definiert ist, in jeden iterierbaren Datentyp einbinden. Die Programmiersprache Ruby löst das beispielsweise über Mixin-Klassen.
@__deets__: `len()` ist aus einer Zeit in der es noch gar keine magischen Methoden wie `__len__()` gab und man tatsächlich nur in C Datentypen schreiben konnte, die eine Länge via `len()` haben. Und ich kann mir schon vorstellen, dass die Optimierung etwas bringt, denn Aufrufe von Funktionen/Methoden sind in Python ja verhältnismässig teuer. Wenn man da in C vorher mal schnell in einem ``struct`` nachschauen kann ob man die Methode überhaupt ausfrufen muss oder sich das sparen kann, bringt das bei den eingebauten Typen schon etwas, ohne dass es bei denen, wo man `__len__()` dann doch aufrufen muss, deutlich langsamer wird.