在Python中,当对空字符串进行分割时,为什么split()返回一个空列表,而split('\n')却返回['']?

194
我正在使用 split('\n') 来获取一个字符串中的行,并发现 ''.split() 返回一个空列表,[],而 ''.split('\n') 返回 ['']。这种差异有没有特定的原因?
还有更方便的方法来计算一个字符串中的行数吗?

2
可能是为什么split()结果中会返回空字符串?的重复问题。 - 0 _
7个回答

275
我正在使用split('\n')来获取一个字符串中的行,发现'' .split()返回一个空列表[],而'' .split('\n')返回['']str.split()方法有两种算法。如果没有给出参数,则它会在连续的空格上进行分割。但是,如果给出参数,则将其视为单个分隔符,没有重复的运行。
在分割空字符串的情况下,第一种模式(无参数)将返回一个空列表,因为空格被吃掉了,并且没有值可以放入结果列表中。
相比之下,第二种模式(带有例如\n的参数)将产生第一个空字段。考虑如果您编写了'\n'.split('\n'),您将获得两个字段(一个拆分,给您两个半部分)。
这种差异有特定的原因吗?
第一种模式在数据以变量数量的空格对齐的列中很有用。例如:
>>> 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']

第二种模式适用于分隔数据,例如CSV,其中重复的逗号表示空字段。例如:
>>> 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']

注意,结果字段的数量比分隔符的数量多一个。想象一下剪绳子。如果你不切割,你只有一条绳子。切一刀,就有两条绳子。切两刀,就有三条绳子。Python的str.split(delimiter)方法也是这样的。
>>> ''.split(',')       # No cuts
['']
>>> ','.split(',')      # One cut
['', '']
>>> ',,'.split(',')     # Two cuts
['', '', '']

问题:还有更方便的方法来计算字符串中的行数吗?
是的,有几种简单的方法。一种使用str.count(),另一种使用str.splitlines()。除非最后一行缺少\n,否则两种方法都会给出相同的答案。如果缺少最后一个换行符,则str.splitlines方法将给出准确的答案。一种更快速且准确的技术使用计数方法,但然后纠正最后一个换行符:
>>> 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    

题目来自@Kaz: 为什么两个非常不同的算法被硬塞到一个函数中?
str.split的签名已经有大约20年的历史了,那个时代的许多API都是严格实用主义的。尽管不是完美的,但该方法的签名也不是“很糟糕”的。在大多数情况下,Guido的API设计选择经得起时间的考验。
当前的API也并非没有优点。考虑像下面这样的字符串:
ps_aux_header  = 'USER               PID  %CPU %MEM      VSZ'
patient_header = 'name,age,height,weight'

当被要求将这些字符串分成字段时,人们倾向于使用同一个英文单词"split"来描述两者。当被要求阅读代码如fields = line.split()fields = line.split(',')时,人们往往能够正确地解释这些语句为"将一行分割成字段"。
微软Excel的text-to-columns tool做出了类似的API选择,并将这两种分割算法合并到同一个工具中。尽管涉及多个算法,但人们似乎在心理上将字段分割视为单个概念。

有没有办法让.split(arg)在分隔符前后没有字符时返回None而不是空字符串?谢谢 - undefined

33

根据文档,似乎这就是它应该工作的方式:

使用指定的分隔符拆分空字符串将返回['']

如果未指定sep或sep为None,则会应用不同的拆分算法:连续的空格被视为单个分隔符,并且如果字符串具有前导或尾随空格,则结果将不包含开头或结尾的空字符串。因此,使用None作为分隔符拆分空字符串或仅由空格组成的字符串将返回[]。

因此,为了使其更清晰,split()函数实现了两种不同的拆分算法,并使用参数的存在来决定要运行哪一个。可能是因为允许对无参数的算法进行优化而不是其他带有参数的算法;我不确定。


8

.split() 在没有参数的情况下尝试变得聪明。它会将任何空格、制表符、空格、换行等都分隔开,并跳过所有由此产生的空字符串。

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

基本上,没有参数的.split()用于从字符串中提取单词,而有参数的.split()只是将一个字符串分割成多个部分。
这就是区别所在。
另外,通过分割来计算行数并不是一种高效的方法。应该计算换行符的数量,并在字符串末尾没有换行符的情况下加一。

4

对于那些真正想要避免调用空字符串的split方法返回['']默认行为的人,这里有两种可能的一行解决方案:

list_ = s.split(*list(sep if s.count(sep) else []))
# Or this
list_ = s.split(sep) if s != "" else []

以下是如何在实际示例中使用第一个方法:

import os

sep = os.linesep  # Split char

s_empty = ""
empty = s_empty.split(*list(sep if s_empty.count(sep) else []))

s_nonempty = f"a{sep}b"
nonempty = s_nonempty.split(*list(sep if s_nonempty.count(sep) else []))

print(f"Empty string: {empty}, non-empty string: {nonempty}")

2
使用count()函数:
s = "Line 1\nLine2\nLine3"
n_lines = s.count('\n') + 1

4
仅在文本不以“\n”结尾时才应执行+1操作。 - Lennart Regebro
8
如果以 "\n" 结尾,那么最后一行是空行。虽然没什么用,但它仍然算作一行,是吧? - Jakub M.
3
不。当我将三行文本写入文件并在每行末尾加上换行符时,那么这个文件就包含了三行。在Unix中,最佳实践是始终以一个换行符结束文本文件。否则,使用“cat file”命令会破坏您的命令行,并导致Subversion出现问题。vi编辑器会自动添加一个换行符。 - 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.

请注意最后一句话。
要计算行数,您只需计算有多少个\n
line_count = some_string.count('\n') + some_string[-1] != '\n'

最后一个部分考虑了最后一行没有以\n结束的情况,尽管这意味着Hello, World!Hello, World!\n有相同的行数(我觉得这很合理),否则你可以将\n的计数加上1

0

要计算行数,可以数换行符的数量:

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

编辑:

另一个答案 带有内置的 count 更加合适,实际上


3
除了使用 count 方法外,布尔值也可以相加(实际上,它们是int的子类),因此生成器表达式可以写成 sum(s == "\n" for s in the_string) - lvc
现在你只是在计算空行吗? - Thijs van Dien
是的,我不会丢弃任何空行。 - Jakub M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接