Warum ist es string.join (Liste) anstelle von list.join (Zeichenfolge)?

1762

Das hat mich immer verwirrt. Es scheint, als wäre das schöner:

my_list = ["Hello", "world"]
print(my_list.join("-"))
# Produce: "Hello-world"

Als das:

my_list = ["Hello", "world"]
print("-".join(my_list))
# Produce: "Hello-world"

Gibt es einen bestimmten Grund dafür?

Evan Fosmark
quelle
1
-Erklärt zum einfachen Speichern und Verstehen, dass Sie einer Liste beitreten und in eine Zeichenfolge konvertieren. Sie ist ergebnisorientiert.
Kalkül
11
@JawSaw: Das verwirrt mem einfach mehr.
Einpoklum
34
Ich denke, die kurze Antwort lautet, dass das Typensystem von Python nicht stark genug ist und es einfacher war, diese Funktionalität einmal strzu implementieren, als sie auf jedem iterierbaren Typ zu implementieren.
BallpointBen
3
Ich denke, die ursprüngliche Idee ist, dass join (), da es einen String zurückgibt, aus dem String-Kontext aufgerufen werden muss. Das Einfügen von join () in eine Liste macht keinen Sinn, da eine Liste ein Container mit Objekten ist und keine einmalige Funktion haben sollte, die nur für Zeichenfolgen gilt.
Joshua Burns

Antworten:

1248

Dies liegt daran, dass jedes iterable Element verbunden werden kann (z. B. Liste, Tupel, Diktat, Set), aber das Ergebnis und der "Joiner" müssen Zeichenfolgen sein.

Zum Beispiel:

'_'.join(['welcome', 'to', 'stack', 'overflow'])
'_'.join(('welcome', 'to', 'stack', 'overflow'))
'welcome_to_stack_overflow'

Wenn Sie etwas anderes als Zeichenfolgen verwenden, wird der folgende Fehler ausgegeben:

TypeError: Sequenzelement 0: erwartete str-Instanz, int gefunden

rekursiv
quelle
57
Ich stimme konzeptionell nicht zu, auch wenn es codeweise sinnvoll ist. list.join(string)erscheint eher als objektorientierter Ansatz, string.join(list)klingt für mich jedoch viel prozeduraler.
Eduardo Pignatelli
22
Warum ist es nicht auf iterable implementiert?
Steen Schütt
10
@TimeSheep: Eine Liste von Ganzzahlen hat keine sinnvolle Verknüpfung, obwohl sie iterierbar ist.
rekursiv
16
Ich habe versucht zu verwenden print(str.join('-', my_list))und es funktioniert, fühlt sich besser an.
Pimgeek
13
@TimeSheep Da iterable kein konkreter Typ ist, ist iterable eine Schnittstelle, ein beliebiger Typ, der eine __iter__Methode definiert . Das Erfordernis, dass alle Iterables auch implementiert werden, joinwürde eine allgemeine Schnittstelle (die auch Iterables über Nicht-Strings abdeckt) für einen ganz bestimmten Anwendungsfall komplizieren. Das Definieren joinauf Strins-Seitenschritten dieses Problems auf Kosten der "nicht intuitiven" Bestellung. Eine bessere Wahl wäre es gewesen, eine Funktion beizubehalten, wobei das erste Argument iterierbar und das zweite (optional) die Joiner-Zeichenfolge ist - aber dieses Schiff ist gesegelt.
user4815162342
319

Dies wurde in den String-Methoden besprochen ... schließlich in das Python-Dev-Archiv eingefügt und von Guido akzeptiert. Dieser Thread begann im Juni 1999 und str.joinwar in Python 1.6 enthalten, das im September 2000 veröffentlicht wurde (und Unicode unterstützte). Python 2.0 ( streinschließlich unterstützter Methoden join) wurde im Oktober 2000 veröffentlicht.

  • In diesem Thread wurden vier Optionen vorgeschlagen:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join als eingebaute Funktion
  • Guido wollte nicht nur lists, tuples, sondern alle Sequenzen / Iterables unterstützen.
  • seq.reduce(str) ist schwierig für Neuankömmlinge.
  • seq.join(str) führt eine unerwartete Abhängigkeit von Sequenzen zu str / unicode ein.
  • join()als eingebaute Funktion würde nur bestimmte Datentypen unterstützen. Die Verwendung eines integrierten Namespace ist also nicht gut. Wenn join()viele Datentypen unterstützt werden, wäre es schwierig, eine optimierte Implementierung zu erstellen. Wenn sie mit der __add__Methode implementiert wird, ist sie O (n²).
  • Die Trennzeichenfolge ( sep) sollte nicht weggelassen werden. Explizit ist besser als implizit.

In diesem Thread werden keine weiteren Gründe angegeben.

Hier sind einige zusätzliche Gedanken (meine eigenen und die meines Freundes):

  • Die Unicode-Unterstützung kam, war aber nicht endgültig. Zu diesem Zeitpunkt war UTF-8 am wahrscheinlichsten im Begriff, UCS2 / 4 zu ersetzen. Um die Gesamtpufferlänge von UTF-8-Zeichenfolgen zu berechnen, muss die Zeichencodierungsregel bekannt sein.
  • Zu diesem Zeitpunkt hatte Python bereits eine gemeinsame Regel für die Sequenzschnittstelle festgelegt, nach der ein Benutzer eine sequenzähnliche (iterierbare) Klasse erstellen konnte. Python unterstützte die Erweiterung der integrierten Typen jedoch erst ab 2.2. Zu dieser Zeit war es schwierig, eine grundlegende iterierbare Klasse bereitzustellen (die in einem anderen Kommentar erwähnt wird).

Guidos Entscheidung wird in einer historischen Mail festgehalten und entscheidet über str.join(seq):

Komisch, aber es scheint richtig! Barry,
mach schon ... - Guido van Rossum

Yoshiki Shibukawa
quelle
251

Weil sich die join()Methode in der Zeichenfolgenklasse befindet, anstatt in der Listenklasse?

Ich bin damit einverstanden, dass es lustig aussieht.

Siehe http://www.faqs.org/docs/diveintopython/odbchelper_join.html :

Historischer Hinweis.Als ich Python zum ersten Mal lernte, erwartete ich, dass join eine Methode einer Liste ist, die das Trennzeichen als Argument verwendet. Vielen Menschen geht es genauso, und hinter der Join-Methode steckt eine Geschichte. Vor Python 1.6 hatten Zeichenfolgen nicht alle diese nützlichen Methoden. Es gab ein separates String-Modul, das alle String-Funktionen enthielt. Jede Funktion nahm eine Zeichenfolge als erstes Argument. Die Funktionen wurden als wichtig genug erachtet, um sie selbst auf die Zeichenfolgen zu setzen, was für Funktionen wie Lower, Upper und Split Sinn machte. Aber viele Hardcore-Python-Programmierer lehnten die neue Join-Methode ab und argumentierten, dass sie stattdessen eine Methode der Liste sein sollte oder dass sie sich überhaupt nicht bewegen sollte, sondern einfach ein Teil des alten String-Moduls bleiben sollte (das immer noch viele enthält von nützlichen Sachen drin).

--- Mark Pilgrim, tauche ein in Python

Bill Karwin
quelle
12
Die Python 3- stringBibliothek hat alle redundanten strMethoden entfernt, sodass Sie sie nicht mehr verwenden können string.join(). Persönlich hätte ich es nie für "lustig" gehalten, es macht vollkommen Sinn, da man viel mehr als nur Listen beitreten kann, aber der Schreiner ist immer eine Zeichenfolge!
Martijn Pieters
67

Ich stimme zu, dass es zunächst nicht intuitiv ist, aber es gibt einen guten Grund. Join kann keine Methode einer Liste sein, weil:

  • es muss auch für verschiedene iterables funktionieren (Tupel, Generatoren usw.)
  • Es muss sich zwischen verschiedenen Arten von Zeichenfolgen unterschiedlich verhalten.

Es gibt tatsächlich zwei Join-Methoden (Python 3.0):

>>> b"".join
<built-in method join of bytes object at 0x00A46800>
>>> "".join
<built-in method join of str object at 0x00A28D40>

Wenn join eine Methode einer Liste wäre, müsste sie ihre Argumente überprüfen, um zu entscheiden, welche von ihnen aufgerufen werden soll. Und Sie können Byte und Str nicht zusammenfügen, daher ist die Art und Weise, wie sie es jetzt haben, sinnvoll.

Kiv
quelle
45

Warum ist es string.join(list)statt list.join(string)?

Dies liegt daran, dass joines sich um eine "String" -Methode handelt! Es wird eine Zeichenfolge aus einer beliebigen iterierbaren Zeichenfolge erstellt. Wenn wir die Methode auf Listen setzen, was ist dann, wenn wir Iterables haben, die keine Listen sind?

Was ist, wenn Sie ein Tupel von Zeichenfolgen haben? Wenn dies eine listMethode wäre, müssten Sie jeden solchen Iterator von Strings wie listzuvor umwandeln, bevor Sie die Elemente zu einem einzigen String zusammenfügen könnten! Zum Beispiel:

some_strings = ('foo', 'bar', 'baz')

Lassen Sie uns unsere eigene List Join-Methode rollen:

class OurList(list): 
    def join(self, s):
        return s.join(self)

Um es zu verwenden, müssen Sie zuerst eine Liste aus jeder Iterable erstellen, um die Zeichenfolgen in dieser Iterable zu verknüpfen, wodurch sowohl Speicher als auch Verarbeitungsleistung verschwendet werden:

>>> l = OurList(some_strings) # step 1, create our list
>>> l.join(', ') # step 2, use our list join method!
'foo, bar, baz'

Wir müssen also einen zusätzlichen Schritt hinzufügen, um unsere Listenmethode zu verwenden, anstatt nur die integrierte String-Methode zu verwenden:

>>> ' | '.join(some_strings) # a single step!
'foo | bar | baz'

Leistungsbeschränkung für Generatoren

Der Algorithmus, mit dem Python die endgültige Zeichenfolge erstellt, str.joinmuss die iterierbare Zeichenfolge tatsächlich zweimal durchlaufen. Wenn Sie ihm also einen Generatorausdruck bereitstellen, muss er zuerst in einer Liste materialisiert werden, bevor die endgültige Zeichenfolge erstellt werden kann.

Während das Weitergeben von Generatoren normalerweise besser ist als das Listenverständnis, str.joinist dies eine Ausnahme:

>>> import timeit
>>> min(timeit.repeat(lambda: ''.join(str(i) for i in range(10) if i)))
3.839168446022086
>>> min(timeit.repeat(lambda: ''.join([str(i) for i in range(10) if i])))
3.339879313018173

Trotzdem ist die str.joinOperation immer noch semantisch eine "String" -Operation, so dass es immer noch sinnvoll ist, sie auf dem strObjekt zu haben als auf verschiedenen iterablen.

Aaron Hall
quelle
24

Betrachten Sie es als die natürliche orthogonale Operation zum Teilen.

Ich verstehe, warum es auf alles Iterierbare anwendbar ist und daher nicht einfach nur auf der Liste implementiert werden kann .

Aus Gründen der Lesbarkeit würde ich es gerne in der Sprache sehen, aber ich denke nicht, dass dies tatsächlich machbar ist. Wenn die Iterierbarkeit eine Schnittstelle wäre, könnte sie der Schnittstelle hinzugefügt werden, aber es handelt sich nur um eine Konvention, und daher gibt es keinen zentralen Weg dazu Fügen Sie es der Menge der Dinge hinzu, die iterierbar sind.

Andy Dent
quelle
13

In erster Linie, weil das Ergebnis von a someString.join()eine Zeichenfolge ist.

Die Sequenz (Liste oder Tupel oder was auch immer) erscheint nicht im Ergebnis, sondern nur als Zeichenfolge. Da das Ergebnis eine Zeichenfolge ist, ist es als Methode einer Zeichenfolge sinnvoll.

S.Lott
quelle
10

- in "-". join (my_list) deklariert, dass Sie eine Liste aus dem Verknüpfen von Elementen in eine Zeichenfolge konvertieren. Sie ist ergebnisorientiert (nur zum einfachen Speichern und Verstehen).

Ich mache ein ausführliches Cheatsheet von Methods_of_string als Referenz.

string_methonds_44 = {
    'convert': ['join','split', 'rsplit','splitlines', 'partition', 'rpartition'],
    'edit': ['replace', 'lstrip', 'rstrip', 'strip'],
    'search': ['endswith', 'startswith', 'count', 'index', 'find','rindex', 'rfind',],
    'condition': ['isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isnumeric','isidentifier',
                  'islower','istitle', 'isupper','isprintable', 'isspace', ],
    'text': ['lower', 'upper', 'capitalize', 'title', 'swapcase',
             'center', 'ljust', 'rjust', 'zfill', 'expandtabs','casefold'],
    'encode': ['translate', 'maketrans', 'encode'],
    'format': ['format', 'format_map']}
Infinitesimalrechnung
quelle
3

Beide sind nicht nett.

string.join (xs, delimit) bedeutet, dass das String-Modul die Existenz einer Liste kennt, über die es nichts zu wissen hat, da das String-Modul nur mit Strings arbeitet.

list.join (delimit) ist ein bisschen netter, weil wir es so gewohnt sind, dass Strings ein grundlegender Typ sind (und das sind sie auch). Dies bedeutet jedoch, dass Join dynamisch versendet werden muss, da im beliebigen Kontext vona.split("\n") ausgelöst werden muss, Python-Compilers möglicherweise nicht bekannt ist, was ein ist, und es nachschlagen muss (analog zur vtable-Suche), was teuer ist, wenn Sie es häufig tun mal.

Wenn der Python-Laufzeit-Compiler weiß, dass list ein integriertes Modul ist, kann er die dynamische Suche überspringen und die Absicht direkt in den Bytecode codieren, während er andernfalls "join" von "a" dynamisch auflösen muss, was mehrere Ebenen umfassen kann der Vererbung pro Aufruf (da sich zwischen den Aufrufen die Bedeutung des Joins möglicherweise geändert hat, da Python eine dynamische Sprache ist).

Leider ist dies der ultimative Fehler der Abstraktion. Unabhängig davon, für welche Abstraktion Sie sich entscheiden, ist Ihre Abstraktion nur im Kontext des Problems sinnvoll, das Sie lösen möchten, und als solches können Sie niemals eine konsistente Abstraktion haben, die nicht mit den zugrunde liegenden Ideologien unvereinbar wird, wenn Sie anfangen, sie zu verkleben zusammen, ohne sie in eine Ansicht zu verpacken, die Ihrer Ideologie entspricht. Wenn Sie dies wissen, ist der Ansatz von Python flexibler, da er billiger ist. Sie müssen mehr bezahlen, damit er "schöner" aussieht, indem Sie entweder Ihren eigenen Wrapper oder Ihren eigenen Präprozessor erstellen.

Dmitry
quelle
0

Die Variablen my_listund "-"sind beide Objekte. Insbesondere sind sie Instanzen der Klassen listund str, respectively. Die joinFunktion gehört zur Klasse str. Daher ist die Syntax "-".join(my_list)wird benutzt , weil das Objekt "-"nimmt my_listals eine Eingabe.

fünfzig Karten
quelle