Warum gibt split () beim Teilen einer leeren Zeichenfolge in Python eine leere Liste zurück, während split ('\ n') [''] zurückgibt?

154

Ich verwende split('\n'), um Zeilen in einer Zeichenfolge abzurufen, und habe festgestellt, dass ''.split()eine leere Liste zurückgegeben wird [], während ''.split('\n')zurückgegeben wird ['']. Gibt es einen bestimmten Grund für einen solchen Unterschied?

Und gibt es eine bequemere Möglichkeit, Zeilen in einer Zeichenfolge zu zählen?

Godice
quelle

Antworten:

247

Frage: Ich verwende split ('\ n'), um Zeilen in einer Zeichenfolge abzurufen, und habe festgestellt, dass '' .split () eine leere Liste [] zurückgibt, während '' .split ('\ n') [''] zurückgibt. .

Die Methode str.split () verfügt über zwei Algorithmen. Wenn keine Argumente angegeben werden, wird es bei wiederholten Leerzeichenläufen aufgeteilt. Wenn jedoch ein Argument angegeben wird, wird es als ein einzelnes Trennzeichen ohne wiederholte Läufe behandelt.

Wenn Sie eine leere Zeichenfolge aufteilen, gibt der erste Modus (kein Argument) eine leere Liste zurück, da das Leerzeichen gegessen wird und keine Werte in die Ergebnisliste aufgenommen werden müssen.

Im Gegensatz dazu erzeugt der zweite Modus (mit einem Argument wie \n) das erste leere Feld. Überlegen Sie, wenn Sie geschrieben hätten '\n'.split('\n'), würden Sie zwei Felder erhalten (ein Split ergibt zwei Hälften).

Frage: Gibt es einen bestimmten Grund für einen solchen Unterschied?

Dieser erste Modus ist nützlich, wenn Daten in Spalten mit variablen Leerzeichen ausgerichtet werden. Beispielsweise:

>>> data = '''\
Shasta      California     14,200
McKinley    Alaska         20,300
Fuji        Japan          12,400
'''
>>> for line in data.splitlines():
        print line.split()

['Shasta', 'California', '14,200']
['McKinley', 'Alaska', '20,300']
['Fuji', 'Japan', '12,400']

Der zweite Modus ist nützlich für begrenzte Daten wie CSV, bei denen wiederholte Kommas leere Felder bezeichnen. Beispielsweise:

>>> data = '''\
Guido,BDFL,,Amsterdam
Barry,FLUFL,,USA
Tim,,,USA
'''
>>> for line in data.splitlines():
        print line.split(',')

['Guido', 'BDFL', '', 'Amsterdam']
['Barry', 'FLUFL', '', 'USA']
['Tim', '', '', 'USA']

Beachten Sie, dass die Anzahl der Ergebnisfelder um eins größer ist als die Anzahl der Begrenzer. Denken Sie daran, ein Seil zu schneiden. Wenn Sie keine Schnitte machen, haben Sie ein Stück. Wenn Sie einen Schnitt machen, erhalten Sie zwei Stücke. Wenn Sie zwei Schnitte machen, erhalten Sie drei Stücke. Und so ist es auch mit Pythons str.split (Trennzeichen) -Methode:

>>> ''.split(',')       # No cuts
['']
>>> ','.split(',')      # One cut
['', '']
>>> ',,'.split(',')     # Two cuts
['', '', '']

Frage: Und gibt es eine bequemere Möglichkeit, Zeilen in einer Zeichenfolge zu zählen?

Ja, es gibt einige einfache Möglichkeiten. Einer verwendet str.count () und der andere str.splitlines () . Beide Methoden geben die gleiche Antwort, es sei denn, in der letzten Zeile fehlt die \n. Wenn die letzte neue Zeile fehlt, gibt der str.splitlines- Ansatz die genaue Antwort. Eine schnellere Technik, die ebenfalls genau ist, verwendet die Zählmethode, korrigiert sie dann jedoch für die endgültige neue Zeile:

>>> data = '''\
Line 1
Line 2
Line 3
Line 4'''

>>> data.count('\n')                               # Inaccurate
3
>>> len(data.splitlines())                         # Accurate, but slow
4
>>> data.count('\n') + (not data.endswith('\n'))   # Accurate and fast
4    

Frage von @Kaz: Warum zum Teufel sind zwei sehr unterschiedliche Algorithmen in einer einzigen Funktion zusammengefasst?

Die Signatur für str.split ist ungefähr 20 Jahre alt, und einige der APIs aus dieser Zeit sind streng pragmatisch. Die Methodensignatur ist zwar nicht perfekt, aber auch nicht "schrecklich". Die API-Designentscheidungen von Guido haben sich größtenteils bewährt.

Die aktuelle API ist nicht ohne Vorteile. Betrachten Sie Zeichenfolgen wie:

ps_aux_header  = "USER               PID  %CPU %MEM      VSZ"
patient_header = "name,age,height,weight"

Wenn Leute gebeten werden, diese Zeichenfolgen in Felder zu unterteilen, beschreiben sie beide mit demselben englischen Wort "split". Wenn Benutzer aufgefordert werden, Code wie fields = line.split() oder zu lesen fields = line.split(','), interpretieren sie die Anweisungen in der Regel korrekt als "Aufteilen einer Zeile in Felder".

Das Text-zu-Spalten-Tool von Microsoft Excel hat eine ähnliche API-Auswahl getroffen und beide Aufteilungsalgorithmen in dasselbe Tool integriert. Menschen scheinen die Feldaufteilung mental als ein einziges Konzept zu modellieren, obwohl mehr als ein Algorithmus beteiligt ist.

Raymond Hettinger
quelle
28

Laut Dokumentation scheint es einfach so zu sein, wie es funktionieren soll :

Das Teilen einer leeren Zeichenfolge mit einem angegebenen Trennzeichen wird zurückgegeben [''].

Wenn sep nicht angegeben ist oder None ist, wird ein anderer Aufteilungsalgorithmus angewendet: Läufe aufeinanderfolgender Leerzeichen werden als einzelnes Trennzeichen betrachtet, und das Ergebnis enthält am Anfang oder Ende keine leeren Zeichenfolgen, wenn die Zeichenfolge führende oder nachfolgende Leerzeichen enthält. Wenn Sie also eine leere Zeichenfolge oder eine Zeichenfolge, die nur aus Leerzeichen besteht, mit einem Trennzeichen "Keine" teilen, wird [] zurückgegeben.

Um es klarer zu machen, split()implementiert die Funktion zwei verschiedene Aufteilungsalgorithmen und verwendet das Vorhandensein eines Arguments, um zu entscheiden, welcher ausgeführt werden soll. Dies kann daran liegen, dass das für mehr Argumente als das mit Argumenten optimiert werden kann. Ich weiß es nicht.

entspannen
quelle
4

.split()ohne Parameter versucht klug zu sein. Es teilt sich in Leerzeichen, Tabulatoren, Leerzeichen, Zeilenvorschübe usw. auf und überspringt dadurch auch alle leeren Zeichenfolgen.

>>> "  fii    fbar \n bopp ".split()
['fii', 'fbar', 'bopp']

Im Wesentlichen werden .split()ohne Parameter Wörter aus einer Zeichenfolge extrahiert, im Gegensatz zu .split()Parametern, bei denen nur eine Zeichenfolge verwendet und aufgeteilt wird.

Das ist der Grund für den Unterschied.

Und ja, das Zählen von Linien durch Teilen ist kein effizienter Weg. Zählen Sie die Anzahl der Zeilenvorschübe und fügen Sie einen hinzu, wenn die Zeichenfolge nicht mit einem Zeilenvorschub endet.

Lennart Regebro
quelle
2

Verwendung count():

s = "Line 1\nLine2\nLine3"
n_lines = s.count('\n') + 1
Gareth Webber
quelle
4
Die + 1 sollte nur erfolgen, wenn der Text nicht mit '\ n' endet.
Lennart Regebro
8
Wenn es mit "\ n" endet, ist die letzte Zeile eine leere Zeile. Obwohl nutzlos, zählt es immer noch als Linie, nein?
Jakub M.
2
Nein. Wenn ich 3 Textzeilen in eine Datei schreibe und jede mit einem Zeilenvorschub beende, würde ich sagen, dass die Datei 3 Zeilen enthält. Unter Unix empfiehlt es sich, eine Textdatei immer mit einem Zeilenvorschub zu beenden. Andernfalls cat fileverstümmelt sich Ihre Befehlszeile und Subversion beschwert sich. vi fügt immer einen an.
user829755
2
>>> print str.split.__doc__
S.split([sep [,maxsplit]]) -> list of strings

Return a list of the words in the string S, using sep as the
delimiter string.  If maxsplit is given, at most maxsplit
splits are done. If sep is not specified or is None, any
whitespace string is a separator and empty strings are removed
from the result.

Beachten Sie den letzten Satz.

Um Zeilen zu zählen, können Sie einfach zählen, wie viele \nes gibt:

line_count = some_string.count('\n') + some_string[-1] != '\n'

Der letzte Teil berücksichtigt die letzte Zeile , die mit nicht enden \n, auch wenn dies bedeutet , dass Hello, World!und Hello, World!\ndie gleiche Zeilenzahl hat (was für mich sinnvoll ist), ansonsten können Sie fügen Sie einfach 1auf die Zahl \n.

Bakuriu
quelle
0

Um Zeilen zu zählen, können Sie die Anzahl der Zeilenumbrüche zählen:

n_lines = sum(1 for s in the_string if s == "\n") + 1 # add 1 for last line

Bearbeiten :

Die andere Antwort mit eingebautem countist eigentlich besser geeignet

Jakub M.
quelle
3
Abgesehen von der countbloßen Verwendung können Bools hinzugefügt werden (tatsächlich sind sie Unterklassen int), sodass das Genexp als geschrieben werden kann sum(s == "\n" for s in the_string).
lvc
Im Moment zählen Sie nur leere Zeilen?
Thijs van Dien
Ja, ich verwerfe keine leeren Zeilen
Jakub M.