【问题标题】:Why is it string.join(list) instead of list.join(string)?为什么是 string.join(list) 而不是 list.join(string)?
【发布时间】:2010-10-04 09:01:25
【问题描述】:

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

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

比这个:

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

这样有什么特殊原因吗?

【问题讨论】:

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

标签: python string list


【解决方案1】:

这在 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 作为内置函数
  • Guido 不仅希望支持 lists 和 tuples,还希望支持所有序列/可迭代对象。
  • 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 才支持扩展内置类型。当时很难提供基本的iterable 类(在另一条评论中提到)。

Guido 的决定记录在historical mail,决定于str.join(seq)

很有趣,但看起来确实是对的!巴里,加油...
吉多·范罗森

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      这是因为任何iterable都可以被join(例如,list、tuple、dict、set),但它的内容和“joiner”必须是字符串。

      例如:

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

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

      TypeError: sequence item 0: expected str instance, int found
      

      【讨论】:

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

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

      【讨论】:

        【解决方案5】:

        两者都不好。

        string.join(xs, delimit) 表示字符串模块知道存在一个列表,它不知道它的业务,因为字符串模块只适用于字符串。

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

        如果python运行时编译器知道list是一个内置模块,它可以跳过动态查找,直接将intent编码到字节码中,否则它需要动态解析“a”的“join”,这可能是每次调用向上继承几层(由于调用之间,join的含义可能发生了变化,因为python是一种动态语言)。

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

        【讨论】:

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

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

        我同意这看起来很有趣。

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

        历史记录。当我第一次学习时 Python,我希望 join 是一种方法 的列表,这将采取 分隔符作为参数。很多 人们也有同样的感觉,而且有 join 方法背后的故事。事先的 到 Python 1.6,字符串并没有全部 这些有用的方法。有一个 单独的字符串模块,其中包含 所有字符串函数;每个 函数将字符串作为第一个 争论。函数被认为 重要到足以放在 字符串本身,这是有道理的 对于像 lower、upper 和 分裂。但是很多铁杆Python 程序员反对新加入 方法,认为它应该是一个 代替列表的方法,或者它 根本不应该移动,而只是停留 旧字符串模块的一部分(其中 里面还有很多有用的东西)。 我专门使用新的加入方法, 但是您会看到编写的代码 方式,如果它真的困扰你,你 可以使用旧的 string.join 函数 而是。

        --- Mark Pilgrim,潜入 Python

        【讨论】:

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

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

        【讨论】:

          【解决方案8】:

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

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

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

          【讨论】:

            【解决方案9】:

            我同意一开始这是违反直觉的,但这是有充分理由的。 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 连接在一起,所以他们现在拥有它的方式是有意义的。

            【讨论】:

              【解决方案10】:

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

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

              【讨论】:

                猜你喜欢
                • 2012-06-27
                • 2013-03-15
                • 2011-02-22
                • 1970-01-01
                • 1970-01-01
                • 2011-03-10
                • 1970-01-01
                • 2011-12-16
                相关资源
                最近更新 更多