Python join:为什么是string.join(list)而不是list.join(string)?

 2019-04-05 

这一直困扰着我。看起来这样更好:

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

比这个:

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

有什么特别的原因吗?


这是因为任何可迭代的都可以连接,不仅仅是列表,结果和"joiner"都是字符串。

例句:

1
2
3
4
5
import urllib2
print '
############
'
.join(
    urllib2.urlopen('http://data.stackexchange.com/users/7095'))


这在String方法中讨论过…最后线程在Python-Dev中实现,并被圭多接受。这个线程开始于1999年6月,str.join包含在2000年9月发布的Python 1.6中(支持Unicode)。Python 2.0(支持str方法,包括join)于2000年10月发布。

这篇文章提出了四种选择:str.join(seq)seq.join(str)seq.reduce(str)join作为一个内置函数Guido不仅希望支持listtuple,而且希望支持所有序列/迭代。对新来者来说很难。seq.join(str)引入了从序列到str/unicode的意外依赖关系。join()作为内置函数只支持特定的数据类型。因此,使用内建的名称空间并不好。如果join()支持许多数据类型,那么创建优化的实现将是困难的,如果使用__add__方法实现,则是O(n2)。分隔符字符串(sep)不应该被省略。显式比隐式好。

本帖中没有提供其他原因。

以下是一些额外的想法(我自己的,我朋友的):

Unicode支持即将到来,但它不是最终的。当时UTF-8最有可能取代UCS2/4。为了计算UTF-8字符串的总缓冲区长度,需要知道字符编码规则。那时,Python已经决定了一个公共序列接口规则,在这个规则中,用户可以创建一个类似于序列的(可迭代的)类。但是Python直到2.2才支持扩展内置类型。当时很难提供基本的可迭代类(另一条评论中提到了这一点)。

Guido的决定记录在历史邮件中,决定str.join(seq):

Funny, but it does seem right! Barry, go for it...
--Guido van Rossum


因为join()方法在string类中,而不是list类中?

我同意这看起来很有趣。

见http://www.faqs.org/docs/diveintopython/odbchelper_join.html。

Historical note. When I first learned
Python, I expected join to be a method
of a list, which would take the
delimiter as an argument. Lots of
people feel the same way, and there’s
a story behind the join method. Prior
to Python 1.6, strings didn’t have all
these useful methods. There was a
separate string module which contained
all the string functions; each
function took a string as its first
argument. The functions were deemed
important enough to put onto the
strings themselves, which made sense
for functions like lower, upper, and
split. But many hard-core Python
programmers objected to the new join
method, arguing that it should be a
method of the list instead, or that it
shouldn’t move at all but simply stay
a part of the old string module (which
still has lots of useful stuff in it).
I use the new join method exclusively,
but you will see code written either
way, and if it really bothers you, you
can use the old string.join function
instead.

--- Mark Pilgrim, Dive into Python


我同意一开始这是违反直觉的,但有一个很好的理由。Join不能是列表的方法,因为:

它还必须适用于不同的迭代器(元组、生成器等)。它必须在不同类型的字符串之间具有不同的行为。

实际上有两种连接方法(Python 3.0):

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

如果join是一个列表的方法,那么它必须检查它的参数来决定调用哪个参数。你不能把byte和str连接在一起,所以它们现在的方式是有意义的。


Why is it string.join(list) instead of list.join(string)?

这是因为join是一个"string"方法!它从任何迭代器创建一个字符串。如果我们把这个方法放在列表上,那么当我们有不是列表的迭代器时呢?

如果你有一个字符串元组呢?如果这是一个list方法,那么在将元素连接成一个字符串之前,必须将每一个这样的字符串迭代器转换为list !例如:

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

让我们滚动我们自己的列表连接方法:

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

要使用它,请注意,我们必须首先从每个iterable创建一个列表来连接该iterable中的字符串,浪费内存和处理能力:

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

所以我们看到我们必须添加一个额外的步骤来使用我们的列表方法,而不是仅仅使用内建字符串方法:

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

生成器的性能警告

Python用来创建带有str.join的最终字符串的算法实际上必须传递迭代器两次,所以如果您为它提供一个生成器表达式,它必须先将其具体化为一个列表,然后才能创建最终字符串。

因此,虽然传递生成器通常比列表理解更好,但str.join是个例外:

1
2
3
4
5
>>> 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

然而,str.join操作在语义上仍然是一个"string"操作,因此在str对象上使用它比在其他迭代器上使用它更有意义。


把它看作是自然的正交运算。

我理解为什么它适用于任何可迭代的东西,所以不能简单地在列表中实现。

可读性,我想看看它的语言但我不认为这是可行的——如果iterability接口就可以添加到接口,但它只是一个会议,所以没有中央的方法将其添加到组东西iterable。


主要是因为someString.join()的结果是一个字符串。

序列(列表或元组或其他)不会出现在结果中,只是一个字符串。因为结果是一个字符串,所以它作为一个字符串的方法是有意义的。


- in"-".join(my_list)声明您正在从连接列表元素转换为字符串。以结果为导向的。(只是为了便于记忆和理解)

我做了一个详尽的methods_of_string作弊表供您参考。

1
2
3
4
5
6
7
8
9
10
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']}

两者都不好。

字符串。join(xs, delimit)意味着字符串模块知道列表的存在,它不需要知道列表的存在,因为字符串模块只处理字符串。

join(delimit)更好一些,因为我们已经习惯了字符串是一种基本类型(从语言上讲,它们确实是)。然而,这意味着join需要动态地分派,因为在a.split("
")
的任意上下文中,python编译器可能不知道a是什么,并且需要查找它(类似于vtable查找),如果执行很多次,这将非常昂贵。

如果python运行时编译器知道列表是一个建在模块,它可以跳过动态查找和编码意图直接到字节码,而否则需要动态地解决"a"的"加入",这可能是几层的每个电话inheritence(自之间的调用,加入的意义可能已经改变了,因为python是一种动态语言)。

遗憾的是,这是抽象的最终缺陷;无论你选择什么抽象,抽象的背景下才有意义你想解决的问题,这样你可以没有一个一致的抽象,不成为符合潜在的意识形态开始粘合在一起没有包装在一个视图,和你的思想是一致的。知道了这一点,python的方法就更灵活了,因为它更便宜,您可以花更多的钱来让它看起来"更好",可以通过自己制作包装器,也可以使用自己的预处理器。