Ao dividir uma string vazia em Python, por que split () retorna uma lista vazia enquanto split ('\ n') retorna ['']?

154

Eu estou usando split('\n')para obter linhas em uma seqüência de caracteres, e descobriu que ''.split()retorna uma lista vazia,, []enquanto ''.split('\n')retorna ['']. Existe alguma razão específica para essa diferença?

E existe alguma maneira mais conveniente de contar linhas em uma string?

godice
fonte

Respostas:

247

Pergunta: Estou usando split ('\ n') para obter linhas em uma string e descobri que '' .split () retorna lista vazia [], enquanto '' .split ('\ n') retorna [''] .

O método str.split () possui dois algoritmos. Se nenhum argumento for fornecido, ele será dividido em execuções repetidas de espaço em branco. No entanto, se um argumento for fornecido, ele será tratado como um delimitador único, sem execuções repetidas.

No caso de dividir uma sequência vazia, o primeiro modo (sem argumento) retornará uma lista vazia porque o espaço em branco é consumido e não há valores para colocar na lista de resultados.

Por outro lado, o segundo modo (com um argumento como \n) produzirá o primeiro campo vazio. Considere que, se você tivesse escrito '\n'.split('\n'), obteria dois campos (uma divisão, duas metades).

Pergunta: Existe alguma razão específica para essa diferença?

Este primeiro modo é útil quando os dados são alinhados em colunas com quantidades variáveis ​​de espaço em branco. Por exemplo:

>>> 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']

O segundo modo é útil para dados delimitados, como CSV, onde vírgulas repetidas indicam campos vazios. Por exemplo:

>>> 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']

Observe que o número de campos de resultados é um maior que o número de delimitadores. Pense em cortar uma corda. Se você não fizer cortes, você terá uma peça. Fazendo um corte, dá dois pedaços. Fazendo dois cortes, dá três pedaços. E assim é com o método str.split (delimitador) do Python :

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

Pergunta: E existe alguma maneira mais conveniente de contar linhas em uma string?

Sim, existem algumas maneiras fáceis. Um usa str.count () e o outro usa str.splitlines () . Ambas as formas fornecerão a mesma resposta, a menos que a linha final esteja ausente \n. Se a nova linha final estiver ausente, a abordagem str.splitlines fornecerá a resposta precisa. Uma técnica mais rápida, que também é precisa, usa o método count, mas o corrige para a nova linha final:

>>> 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    

Pergunta do @Kaz: Por que diabos existem dois algoritmos muito diferentes em uma única função?

A assinatura do str.split tem cerca de 20 anos e várias APIs daquela época são estritamente pragmáticas. Embora não seja perfeita, a assinatura do método também não é "terrível". Na maioria das vezes, as opções de design da API do Guido resistiram ao teste do tempo.

A API atual não está isenta de vantagens. Considere cadeias de caracteres como:

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

Quando solicitados a dividir essas cadeias em campos, as pessoas tendem a descrever as duas usando a mesma palavra em inglês "dividir". Quando solicitados a ler códigos como fields = line.split() ou fields = line.split(','), as pessoas tendem a interpretar corretamente as instruções como "divide uma linha em campos".

A ferramenta de texto para colunas do Microsoft Excel fez uma escolha de API semelhante e incorpora os dois algoritmos de divisão na mesma ferramenta. As pessoas parecem modelar mentalmente a divisão de campos como um conceito único, embora mais de um algoritmo esteja envolvido.

Raymond Hettinger
fonte
28

Parece simplesmente ser do jeito que deve funcionar, de acordo com a documentação :

A divisão de uma sequência vazia com um separador especificado retorna [''].

Se sep não for especificado ou for None, um algoritmo de divisão diferente será aplicado: execuções de espaços em branco consecutivos serão consideradas como um único separador e o resultado não conterá cadeias vazias no início ou no final se a cadeia tiver espaços em branco à esquerda ou à direita. Consequentemente, a divisão de uma sequência vazia ou de uma sequência composta apenas de espaço em branco com um separador Nenhum retorna [].

Portanto, para tornar mais claro, a split()função implementa dois algoritmos de divisão diferentes e usa a presença de um argumento para decidir qual deles executar. Isso pode ser porque permite otimizar um para nenhum argumento mais do que aquele com argumentos; Eu não sei.

descontrair
fonte
4

.split()sem parâmetros tenta ser inteligente. Ele se divide em qualquer espaço em branco, guias, espaços, feeds de linha etc., e também pula todas as seqüências vazias como resultado disso.

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

Essencialmente, .split()sem parâmetros são usados ​​para extrair palavras de uma string, ao contrário de .split()parâmetros que apenas pegam uma string e a dividem.

Essa é a razão da diferença.

E sim, contar linhas dividindo não é uma maneira eficiente. Conte o número de feeds de linha e adicione um se a sequência não terminar com um feed de linha.

Lennart Regebro
fonte
2

Use count():

s = "Line 1\nLine2\nLine3"
n_lines = s.count('\n') + 1
Gareth Webber
fonte
4
O + 1 deve ser feito apenas se o texto não terminar com '\ n'.
Lennart Regebro 20/05
8
Bem, se terminar com "\ n", a última linha será uma linha vazia. Embora inútil, ainda conta como linha, não?
Jakub M.
2
não. quando escrevo 3 linhas de texto em um arquivo e finalizo cada uma delas com um avanço de linha, diria que o arquivo contém 3 linhas. no unix, é uma prática recomendada que um arquivo de texto sempre termine com um avanço de linha. caso contrário, cat filedistorce sua linha de comando e o subversion reclama. vi sempre acrescenta um.
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.

Observe a última frase.

Para contar linhas, você pode simplesmente contar quantas \n:

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

A última parte leva em consideração a última linha que não termina \n, mesmo que isso signifique isso Hello, World!e Hello, World!\ntenha a mesma contagem de linhas (o que para mim é razoável); caso contrário, você pode simplesmente adicionar 1à contagem de \n.

Bakuriu
fonte
0

Para contar linhas, você pode contar o número de quebras de linha:

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

Editar :

A outra resposta com o built-in counté mais adequada, na verdade

Jakub M.
fonte
3
Além de apenas usar count, os bools são adicionáveis ​​(na verdade, eles são subclasses int), então o genexp pode ser escrito como sum(s == "\n" for s in the_string).
Lvc
Agora você está apenas contando linhas vazias?
Thijs van Dien
Sim, eu não descartar quaisquer linhas vazias
Jakub M.