Wie kann ich mir das merken (Listen)

Probleme bei der Installation?
Antworten
OrhanBencegay
User
Beiträge: 4
Registriert: Mittwoch 8. April 2020, 21:25

Ich bin Python-Anfänger. Vielleicht klingt das für Euch wie eine dumme Frage aber mir ist es verdammt wichtig es zu verstehen. Denn ich weiss in den meisten Fällen zu 90 Prozent was ich verwenden soll, aber nicht, WIE. Hier ein mal ein Beispiel:

Wir haben folgende Liste und folgende Szenarien:

Hinten einen Wert anfügen:
studenten = ["Mark", "Lukas", "Michael", "Friedrich"]
studenten.append("Jens")
print(studenten")

Ermitteln wie lang die Liste ist:
studenten = ["Mark", "Lukas", "Michael", "Friedrich"]
print(len(studenten))

Liste-> String
studenten = ["Mark", "Lukas", "Michael", "Friedrich"]
print(", ".join(studenten))

String in Liste
studenten = "Mark, Lukas, Michael, Friedrich"
print(i.split())

So, bei dem ersten Beispiel nehmen wir zuerst die Variable und dann append"studenten.append"
Beim zweiten Beispiel nehmen wir aber zuerst "len" und dann die Variable und das auch noch in Klammern?!
Das dritte Beispiel hat mich dann ganz vom Hocker gehauen ist aber ähnlich wie das zweite Beispiel.
Und das letzte Beispiel hat Parallelen zum ersten Beispiel

Meine Frage ist, wie kann ich mir das einprägen, wann ich was zuerst verwende? Wann benutze ich z.B. zuerst die definierte Variable und dann "append" zum Beispiel. Oder wann benutze ich zuerst die eine Funktion und dann nachfolgend die Variable? Wie merkt Ihr Euch das? Ich hoffe ich habe mich verständlich erklärt. Wünsche Euch noch einen angenehmen Tag
nezzcarth
User
Beiträge: 1762
Registriert: Samstag 16. April 2011, 12:47

Ich glaube, am einfachsten ist, zu versuchen, das zunächst als gegeben zu akzeptieren. Sobald du in deinem Lernprozess bei Objektorientierung sowie den Interna von Python angekommen bist, wird es mehr Sinn machen. Es kann gut sein, dass die nachfolgende Erklärung mehr Fragen offen lässt, als sie klärt, aber ich versuche es mal:

Python kennt Funktionen und Methoden. Funktionen sind etwas, was Eingabewerte haben kann und eine Rückgabe erzeugt. 'len' ist eine Funktion, die als Parameter ein Objekt erhält und als Rückgabe dessen Länge liefert. Methoden sind so ähnlich, aber an Objekte gebunden auf denen sie operieren. Man kann sie sich als Funktionen vorstellen, die als festen, ersten Parameter immer das Objekt selbst bekommen. Wenn du mit 'l = []' eine Liste erzeugst, erzeugst du ein Objekt vom Typ Liste. Und jeder Typ hat vordefinierte Methoden, die man sich im Interpreter mit der Funktion 'dir()' ansehen, oder in der Doku nachsehen, kann. 'append' ist eine Methode von Listen. Die Methoden eines Objekts werden mit <objekt>.<methode>(<parameter) aufgerufen. Wenn du also schreibst 'l.append("abc")', wird die Methode 'append' des Objekt 'l' aufgerufen und als Resultat der Parameter an die Liste angehängt. Analog dazu sind 'split' und 'join' Methoden von String-Objekten. In Beispiel drei ist ',' auch ein String Objekt. Rufst du die Methode 'join' auf, wird der Teil in Klammern wieder als Eingabe genommen und als Ergebnis ein neuer String geliefert, bei dem ',' als Verbindungselement dient (warum hier ein neues Objekt geliefert wird, aber bei der Liste das existierende Objekt modifiziert wird, ist noch einmal ein separater Punkte; Python unterscheidet zwischen veränderlichen (Mutables) und unveränderlichen (Immutables) Objekten; Listen sind veränderlich, Strings unverändlich). Weshalb 'len' nun eine Funktion und keine Methode ist, ist eine Design-Entscheidung der Pythonentwickler.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Welche Methoden ein Objekt besitzt kann man sich mit help (objekt) auch hervorragens im interaktiven Interpreter anschauen.

Wie nezzcarth bereita schrieb ist es eine Design-Entscheidung, dass len eine eingebaute Funktion ist. Die finde ich nicht ganz so glücklich. Eine [url="https://docs.python.org/3/library/functions.html]Übersicht aller eingebauten Funktionen[/url] hält die Dokumentation bereit. Zum Glück sind die überschaubar.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Probleme die du da beschreibst sind durchaus nachvollziehbar. Es ist eine haeufiger geaeusserte Kritik, dass Python zwar Objekte und Methoden benutzen, und dann eben eine Syntax wie

Code: Alles auswählen

liste.append(element)
zum tragen kommt. Aber an bestimmten Stellen komische Abweichungen hat.

Code: Alles auswählen

liste.len()
koennte genauso funktionieren, und tut das auch in anderen Sprachen. Das es so ist wie es ist, ist einfach eine Frage des persoenlichen Geschmackes. Aehnliches gilt bei der Frage, wie man das join implementiert. Ich persoenlich bevorzuge die Wahl, die Python hier getroffen hat:

Code: Alles auswählen

"string".join(elemente)
Aus verschiedenen Gruenden halte ich das fuer Vorteilhaft:

- elemente kann alles moegliche enthalten, also zB auch Zahlen oder andere Objekte. Nur wenn es Strings sind, kann man damit sinnvoll joinen. Also muss oft sowas passieren:

Code: Alles auswählen

"string".join(str(element) for element in elemente)
Das ist dann deutlich klarer und weniger verwirrend, als wenn man das umdrehen wuerde:

Code: Alles auswählen

[str(element) for element in elemente].join("string")
- elemente kann ALLES sein, was iterierbar ist, und zu sagen, das ALLES was iterierbar ist (und das sind eine Menge Dinge) jetzt IMMER auch join(...) beherrscht, ist ein schlechtes Design.

Am Ende ist es ganz einfach: du musst es nehmen, wie es kommt, denn aendern kann man's eh nicht.
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

OrhanBencegay hat geschrieben: Freitag 10. April 2020, 22:24 Beim zweiten Beispiel nehmen wir aber zuerst "len" und dann die Variable und das auch noch in Klammern?!
Grund der Implementierung von len() als Funktion ist eine Optimierung in Python. Auf selbstdefinierte Klassen wird bei der Anwendung von len(obj) die Methode obj.__len__() aufgerufen, bei eingebauten Datentypen aber wird direkt auf die zugrunde liegende C-Datenstruktur zugegriffen und das Ergebnis geliefert, was erheblich schneller abläuft als ein Methodenaufruf.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@kbr: Woher weisst du das? Man haette auch einfach den Methodenaufruf gleich in die PyObject_HEAD-Struktur einbauen koennen, und dann waere da kein Unterschied aus meiner Sicht. Ich kenne zur Begruendung nur Geschmacksangaben von Guido.
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

@__deets__: Diese Erklärung habe ich aus Fluent Python von Luciano Ramalho, der dies als internen 'shortcut' beschreibt, wonach das ob_size Feld des C-structs PyVarObject direkt ausgelesen wird. Dadurch erübrige sich ein Methodenaufruf.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das das so passiert glaube ich sofort. Das das der GRUND fuer len() statt .len() ist, da habe ich starke Zweifel. Schliesslich kann man ja __len__ implementieren, es muss also eh *immer* ein Lookup gemacht werden, ob's da jetzt eine Methode gibt. Das das einsparen dieser einen Indirektion fuer eine Handvoll Objekte jetzt der Kicker war, kann ich mir nicht vorstellen. Aber interessantes Detail natuerlich.
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

Um zu sehen, ob das jetzt wirklich *viel* bringt, habe ich mal ein Mini-Profiling vorgenommen:

Code: Alles auswählen

In [21]: a = list(range(4))                                                     

In [22]: len(a)                                                                 
Out[22]: 4

In [23]: %timeit len(a)                                                         
59.1 ns ± 0.865 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [24]: class Seq(list): 
    ...:     pass 
    ...:                                                                        

In [25]: b = Seq(range(4))                                                      

In [26]: len(b)                                                                 
Out[26]: 4

In [27]: %timeit len(b)                                                         
61.2 ns ± 0.713 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [28]: class Seq2(list): 
    ...:     def __len__(self): 
    ...:         return super().__len__() 
    ...:                                                                        

In [29]: c = Seq2(range(4))                                                     

In [30]: len(c)                                                                 
Out[30]: 4

In [31]: %timeit len(c)                                                         
372 ns ± 3.12 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [32]: class Seq3: 
    ...:     def __len__(self): 
    ...:         return 4 
    ...:                                                                        

In [33]: d = Seq3()                                                             

In [34]: %timeit len(d)                                                         
136 ns ± 2.04 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
D.h. mit einer aktuellen Python Version (hier 3.7) ist der lookup für die Methode __len__ bei b sehr schnell, da sich die Laufzeiten von a und b kaum unterscheiden (letztendlich ein flotter Dictionary-Zugriff). Sobald die Methode definiert ist, macht sich, wenig überraschend, der Overhead von Funktionsaufrufen bemerkbar.

Interessant ist dann noch der letzte Fall:

Code: Alles auswählen

In [35]: a.__len__()                                                            
Out[35]: 4

In [36]: %timeit a.__len__()                                                    
97.6 ns ± 0.564 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Hier zeigt sich nochmal die Optimierung der Funktion len() auf eingebaute Datentypen einen Methodenaufruf zu vermeiden.
Antworten