ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

比这个:

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

有这样的具体原因吗?

为了便于记忆和理解,- 声明您正在加入列表并转换为字符串。它是面向结果的。
我认为最初的想法是因为 join() 返回一个字符串,所以必须从字符串上下文中调用它。将 join() 放在列表上没有多大意义,因为列表是对象的容器,不应该有一个只针对字符串的一次性函数。
@BallpointBen “...因为 Python 的类型系统不够强大”是完全错误的。正如 Yoshiki Shibukawa 的回答(从您发表评论之前的 8 年开始!)所说, iterable.join() 被认为是可能的,但被拒绝了,因为它是一个不太好的 API 设计 - 而不是因为它无法实现。
我可能有偏见,因为我习惯了javascript,但是你想加入列表,它应该是列表imo的方法。感觉是倒退。
好吧,str.split() 返回一个非字符串,这很有意义。看起来同样的逻辑在这里应该没问题,对吧? (只谈非字符串输出的概念问题)

u
user3840170

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

例如:

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

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

类型错误:序列项 0:预期的 str 实例,找到 int


即使在代码上有意义,我在概念上也不同意。 list.join(string) 看起来更像是一种面向对象的方法,而 string.join(list) 对我来说听起来更程序化。
那么为什么不在可迭代对象上实现呢?
@TimeSheep:整数列表没有有意义的连接,即使它是可迭代的。
我尝试使用 print(str.join('-', my_list)) 并且它有效,感觉更好。
@TimeSheep 因为 iterable 不是具体类型,所以 iterable 是一个接口,任何定义 __iter__ 方法的类型。对于非常特殊的用例,要求所有可迭代对象也实现 join 会使通用接口(也涵盖非字符串上的可迭代对象)复杂化。在字符串上定义 join 以“不直观”顺序为代价绕过了这个问题。一个更好的选择可能是让它成为一个函数,第一个参数是可迭代的,第二个(可选的)是连接字符串 - 但那艘船已经航行了。
Y
Yoshiki Shibukawa

这在 Python-Dev 存档的 String methods... finally 线程中进行了讨论,并被 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 as a built-in function

str.join(seq)

seq.join(str)

seq.reduce(str)

作为内置函数加入

Guido 不仅希望支持列表和元组,还希望支持所有序列/可迭代对象。

seq.reduce(str) 对新手来说很难。

seq.join(str) 引入了从序列到 str/unicode 的意外依赖。

join() 作为内置函数将仅支持特定的数据类型。所以使用内置命名空间并不好。如果 join() 支持多种数据类型,则创建优化的实现将很困难,如果使用 __add__ 方法实现,则它会 O(n²)。

不应省略分隔符字符串 (sep)。显式优于隐式。

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

Unicode 支持即将到来,但它不是最终的。那时 UTF-8 最有可能取代 UCS2/4。要计算 UTF-8 字符串的总缓冲区长度,需要知道字符编码规则。

那时,Python 已经决定了一个通用的序列接口规则,用户可以在其中创建一个类似序列(可迭代)的类。但是 Python 直到 2.2 才支持扩展内置类型。当时很难提供基本的可迭代类(在另一条评论中提到)。

Guido 的决定记录在 historical mail 中,对 str.join(seq) 做出决定:

有趣,但它似乎是正确的!巴里,加油……吉多·范·罗森


很好,这记录了推理。很高兴了解更多关于“从序列到 str/unicode 的意外依赖”。 ——以及是否仍然如此。
这是最好的答案,因为它提供了权威背景和选择它的原因。
C
Community

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

我同意这看起来很有趣。

请参阅http://www.faqs.org/docs/diveintopython/odbchelper_join.html

历史注释。当我第一次学习 Python 时,我期望 join 是一个列表的方法,它将分隔符作为参数。很多人都有同样的感受,join 方法背后有一个故事。在 Python 1.6 之前,字符串没有所有这些有用的方法。有一个单独的字符串模块,其中包含所有字符串函数;每个函数都将一个字符串作为它的第一个参数。这些函数被认为足够重要,可以放在字符串本身上,这对于像 lower、upper 和 split 这样的函数是有意义的。但是许多核心 Python 程序员反对新的 join 方法,认为它应该是列表的一个方法,或者它根本不应该移动,而只是保留旧字符串模块的一部分(它仍然有很多里面有用的东西)。我只使用新的 join 方法,但是你会看到用任何一种方式编写的代码,如果它真的让你感到困扰,你可以使用旧的 string.join 函数来代替。 --- Mark Pilgrim,潜入 Python


Python 3 string 库已删除所有多余的 str 方法,因此您不能再使用 string.join()。就个人而言,我从不认为它“有趣”,它非常有意义,因为您可以加入的不仅仅是列表,但加入者始终是一个字符串!
K
Kiv

我同意一开始这是违反直觉的,但这是有充分理由的。 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>

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


R
Russia Must Remove Putin

为什么是 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'

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

>>> ' | '.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 对象上比放在其他可迭代对象上仍然有意义。


A
Andy Dent

将其视为拆分的自然正交操作。

我理解为什么它适用于任何可迭代的东西,因此不能轻易地在列表中实现。

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


I
Iulian Onofrei

"-".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']}

S
S.Lott

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

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


D
Dmitry

两个都不好看。

string.join(xs, delimit) 意味着字符串模块知道存在一个列表,它不知道该列表的存在,因为字符串模块仅适用于字符串。

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

如果 python 运行时编译器知道 list 是一个内置模块,它可以跳过动态查找并将意图直接编码到字节码中,否则它需要动态解析 "a" 的 "join",这可能需要好几层每个调用的继承(由于调用之间,join的含义可能已经改变,因为python是一种动态语言)。

可悲的是,这是抽象的终极缺陷;无论你选择什么抽象,你的抽象只会在你试图解决的问题的背景下才有意义,因此,当你开始粘合它们时,你永远不可能有一个不会与潜在意识形态不一致的一致抽象在一起,而不是将它们包装在与您的意识形态一致的视图中。知道了这一点,python 的方法更加灵活,因为它更便宜,您可以通过制作自己的包装器或自己的预处理器来支付更多费用以使其看起来“更好”。


“字符串模块知道存在一个列表,它没有业务知道”不是真的。 join() 方法的参数是任何可迭代的,因此 str 不需要了解 list(至少,对于该方法而言)。显然“可迭代”比 str 更基本,因为 str 实际上本身就是可迭代的! (另外,我认为 liststr 更基本,因为 Unicode 字符处理比存储一系列对象要复杂得多,但正如我所说的,它在这里无关紧要。)
“如果python运行时编译器知道列表是一个内置模块,它可以跳过动态查找”(你的意思是“类”而不是“模块”。)这很奇怪。如果 l 是一个列表,而 s 是一个字符串,那么 l.join(s)s.join(l) 都涉及使用类系统进行动态查找。也许如果您使用字符串文字 "-".join(...) 它可以避免它,但这也适用于列表文字 [...].join("-")。我想也许前者更常见。但我认为无论如何都没有完成这种优化,而且正如 Yoshiki 的回答所示,这当然不是决定的原因。
f
fiftytwocards

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


F
Francis Cagney

你不能只加入列表和元组。你可以加入几乎任何可迭代的。可迭代对象包括生成器、地图、过滤器等

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

而不是向所有迭代器授予连接:[...],所有这些迭代器都只有对象作为公共父级。”——这似乎是一个合理的理由(没有 iter.join()