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

这一直让我困惑。看起来这样会更好:

["Hello", "world"].join("-")

比这个:

"-".join(["Hello", "world"])

有没有具体的原因是这样的?

1425215 次浏览

因为join()方法在字符串类中,而不是列表类?

我同意这看起来很有趣。

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

#当我第一次知道Python,我希望加入是一个方法的列表,这将采取分隔符作为参数。很多人们也有同样的感觉加入方法背后的故事到Python 1.6,字符串没有全部这些有用的方法。有一个单独的字符串模块,其中包含所有字符串函数;每个函数将字符串作为其第一个参数。函数被视为重要到足以放在弦本身这就说得通了对于下、上和分裂。但许多核心Python程序员反对新加入方法,认为它应该是一个而不是列表的方法,或者它根本不应该移动,而只是停留旧字符串模块的一部分(其中里面有很多有用的东西)。我只使用新的连接方法,但你也会看到编写的代码如果它真的困扰着你,你可以使用旧的string.join功能

---Mark Pilgrim,Dive into Python

这是因为任何可迭代对象都可以连接(例如,list、tuple、pse、set),但它的内容和“joiner”必须是字符串。

例如:

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

使用字符串以外的东西会引发以下错误:

TypeError:序列项0:预期的str实例,int找到

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

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

我同意一开始这是违反直觉的,但这是有充分理由的。加入不能是列表的方法,因为:

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

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

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

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

把它看作是分裂的自然正交操作。

我理解为什么它适用于任何可迭代的东西,所以不能很容易地在列表中实现只是

为了易读性,我希望在语言中看到它,但我认为这实际上是不可行的-如果可迭代性是一个接口,那么它可以添加到接口中,但它只是一个约定,因此没有中央方法将其添加到可迭代的事物集合中。

这在Python-Dev的字符串方法…终于线程中进行了讨论,并被Guido接受。这个线程始于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.reduce(str)对新人来说很难。
  • seq.join(str)引入了从序列到str/Unicode的意外依赖。
  • join()作为一个独立的内置函数将只支持特定的数据类型。因此使用内置命名空间不好。如果join()支持多种数据类型,创建一个优化的实现将是困难的:如果使用__add__方法实现,那么它将是O(n²)。
  • 不应省略分隔符字符串(sep)。显式比隐式好。

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

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

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

好笑,但看起来是对的!Barry,加油…
吉多·范·罗苏姆

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

这是因为join是一个“字符串”方法!它从任何可迭代对象创建一个字符串。如果我们将方法粘贴在列表上,当我们有不是列表的可迭代对象时怎么办?

如果你有一个字符串元组怎么办?如果这是一个list方法,你必须将每个这样的字符串迭代器转换为list,然后才能将元素连接到单个字符串中!例如:

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

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

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

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

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

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

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

发电机的性能警告

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

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

>>> 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操作在语义上仍然是一个“字符串”操作,因此在str对象上使用它仍然比在杂项迭代上更有意义。

"-".join(my_list)中的-声明你正在从连接元素列表转换为字符串。

我做了一份详尽的methods_of_string备忘单供你参考。

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

变量my_list"-"都是对象。具体来说,它们分别是类liststr的实例。join函数属于类str。因此,使用语法"-".join(my_list)是因为对象"-"my_list作为输入。

您不仅可以加入列表和元组。您几乎可以加入任何可迭代对象。可迭代对象包括生成器、映射、过滤器等

>>> '-'.join(chr(x) for x in range(48, 55))'0-1-2-3-4-5-6'
>>> '-'.join(map(str, (1, 10, 100)))'1-10-100'

使用生成器,地图,过滤器等的美妙之处在于它们花费很少的内存,并且几乎是即时创建的。

这是概念上的另一个原因:

str.join(<iterator>)

只授予str这种能力是有效的。而不是向所有迭代器授予连接:列表、元组、集合、字典、生成器、映射、过滤器所有这些都只有对象作为公共父对象。

当然range()和zip()也是迭代器,但它们永远不会返回str,因此不能与str.join()一起使用

>>> '-'.join(range(48, 55))Traceback (most recent call last):File "<stdin>", line 1, in <module>TypeError: sequence item 0: expected str instance, int found

我100%同意你的问题。如果我们把所有的答案和评论归结起来,解释归结为“历史原因”。

str.join不仅仅是令人困惑或不好看,它在现实世界的代码中是不切实际的。它破坏了可读的函数或方法链,因为分隔符很少(曾经?)是之前一些计算的结果。根据我的经验,它总是一个常量,像", "这样的硬编码值。

我清理我的代码-允许在一个方向上阅读它-使用tools.functoolz

>>> from toolz.functoolz import curry, pipe>>> join = curry(str.join)>>>>>> a = ["one", "two", "three"]>>> pipe(...     a,...     join("; ")>>> )'one; two; three'

我还将在管道中添加其他几个函数。结果是它只在一个方向上很容易读取,从头到尾都是一个函数链。柯里化map很有帮助。