【问题标题】:Python: why str.join(iterable) instead of str.join(*strings)Python:为什么 str.join(iterable) 而不是 str.join(*strings)
【发布时间】:2012-11-09 21:16:36
【问题描述】:

我经常将我的 str.join() 参数包装在一个列表中,例如

'.'.join([str_one, str_two])

额外的列表包装对我来说总是多余的。我想做...

'.'.join(str_one, str_two, str_three, ...)

...或者如果我有一个列表...

'.'.join(*list_of_strings)

是的,我是一个极简主义者,是的,我很挑剔,但大多数时候我只是对这里的历史感到好奇,或者我是否遗漏了什么。也许有一段时间会发生口角?

编辑:

我只想指出 max() 处理这两个版本:

最大(可迭代[,键]) max(arg1, arg2, *args[, key])

【问题讨论】:

  • 您想要额外的“splats”还是额外的列表?除了“splats”可能不会永远存在的事实。 ** 相当新。
  • 这是一个副本,我现在无法搜索原件。答案是该函数以该形式存在,因为它支持加入任何类型的集合(而不仅仅是列表)。你想要的版本基本上没有意义,因为函数仍然作为集合接收参数
  • 可迭代用例更为常见 - 您真正定义数量的唯一变量要对称连接的频率是多少?它们要么是相关的,应该在列表中,要么不是,加入它们是很奇怪的。
  • @user1062565:对于两个字符串,str_one + '.' + str_two 有什么问题?字符更少,更清晰!
  • @pydsigner:我不知道 11 年以上是否“相当新”;)

标签: python


【解决方案1】:

对于简短列表,这无关紧要,您只需输入 2 个字符。但str.join() 最常见的用例(我认为)如下:

''.join(process(x) for x in some_input) 
# or
result = []
for x in some_input:
    result.append(process(x))
''.join(result)

其中 input_data 可以有数千个条目,而您只想高效地生成输出字符串。

如果加入接受的变量参数而不是可迭代的,则必须拼写为:

''.join(*(process(x) for x in some_input))
# or
''.join(*result)

这将创建一个(可能很长)元组,只是将其作为*args 传递。

因此,在短情况下这是 2 个字符,而在大数据情况下是浪费的。

历史记录

(第二次编辑:基于 HISTORY 文件,其中包含所有版本中缺少的版本。感谢 Don。)

函数定义中的*args是很久以前在Python中添加的:

==> 0.9.8 版(1993 年 1 月 9 日)

需要案例 (a) 来容纳可变长度的参数列表; 现在有一个明确的“可变参数”功能(在最后一个参数之前 带有'*')。为了与旧类兼容,需要案例 (b) 定义:直到 0.9.4 版本,一种具有多个参数的方法 必须声明为“def meth(self, (arg1, arg2, ...)): ...”。

将列表传递给此类函数的正确方法是使用内置函数apply(callable, sequence)。 (注意,这里没有提到**kwargs,它最早可以在docs for version 1.4中看到)。

使用* 语法调用函数的能力在 1.6 的发行说明中首次提到:

现在可以使用特殊语法代替 apply() 功能。 f(*args, **kwds) 等价于 apply(f, args, kwds)。你 也可以使用变体 f(a1, a2, *args, **kwds) 并且您可以保留一个 或其他输出:f(args), f(*kwds).

但在version 2.2 之前是missing from grammar docs

在 2.0 之前,str.join() 甚至不存在,您必须这样做 from string import join

【讨论】:

  • 我没有想到列表理解案例。谢谢!
  • 似乎 *args aka varargs 是在 0.9.8 中添加的,请参阅:svn.python.org/projects/python/trunk/Misc/HISTORY 并搜索 vor vararg
  • 你写道,“如果加入接受的变量参数而不是一个可迭代的,这......将创建一个(可能很长的)元组。”但是如果你将一个可迭代对象传递给join,Python 无论如何都会创建一个(可能很长的)列表,所以这没什么区别。请参阅PyUnicode_Join,它调用PySequence_Fast,它将可迭代对象转换为列表。
【解决方案2】:

您必须编写自己的函数才能做到这一点。

>>> def my_join(separator, *args):
        return separator.join(args)

>>> my_join('.', '1', '2', '3')
'1.2.3'

请注意,这并不能避免创建额外对象,它只是隐藏正在创建额外对象。如果您检查 args 的类型,您会发现它是 tuple

如果您不想创建函数并且有固定的字符串列表,那么可以使用格式而不是连接:

'{}.{}.{}.{}'.format(str_one, str_two, str_three, str_four)

最好还是坚持'.'.join((a, b, c))

【讨论】:

  • 我正要解释,但按回车键切换到下一行,这在 cmets 中非常聪明 ;-) 我当然没有投反对票 - 我很少这样做 - 从来没有没有解释
  • 谢谢马克!我的问题不在于如何,而在于为什么。引入自定义连接是多余的,而用于连接两个字符串的 format() 似乎也总是多余的。再说一次,我故意对风格过于挑剔,因为 Python 在风格方面设定了如此高的标准。
  • 我正在寻找关于引入 splash-operator 的参考,因为我觉得它从一开始就没有出现在 python 中,我会推断方法签名 S.join(iterable) -> string 没有由于兼容性原因,t合并splash-operator应该保持大部分不变,就像你不希望最初使用cpython break构建的东西在这些基本问题的另一个实现下
  • @user1062565:为什么只有写str.join的人才能回答。可能是圭多……
  • 我只是没有找到任何关于引入splash运算符的参考。我发现最早的参考资料是针对 python 1.5,所以我不知道“迟到”的介绍(如果不是我的想象,这可能是“为什么”)可能与它有关 - 抱歉。
【解决方案3】:

啊,现在这是一个难题!尝试争论哪种风格更简约......很难在不过于主观的情况下给出一个好的答案,因为这完全是关于惯例。

问题是:我们有一个接受有序集合的函数;它应该接受它作为单个参数还是可变长度参数列表?


Python 通常会回答:单参数; VLAL 如果你真的有理由这样做。让我们看看 Python 库如何反映这一点:

标准库有几个 VLAL 示例,最值得注意的是:

  • 当函数可以用任意数量的单独序列调用时 - 例如zipmapitertools.chain
  • 当有一个序列要传递时,但您并不真正期望调用者将整个序列作为单个变量。这似乎适合str.format

以及使用单个参数的常见情况:

  • 当您想要对单个序列进行一些通用数据处理时。这适合功能性三重奏(map*、reducefilter)及其专门的衍生品,如 sumstr.join。还有像enumerate 这样的有状态转换。
    • 模式是“使用一个可交互对象,给出另一个可迭代对象”或“使用一个可迭代对象,给出一个结果”。

希望这能回答你的问题。


注意:map 在技术上是 var-arg,但常见的用例只是 map(func, sequence) -> sequencereducefilter 属于一个桶。

*模糊的情况,map(func, *sequences) 在概念上类似于 map(func, izip_longest(sequences)) - 之前解释了 zips 遵循 var-arg 约定的原因。

我希望你在这里遵循我的想法;毕竟这都是编程风格的问题,我只是指出 Python 库函数中的一些模式。

【讨论】:

  • 我认为map(func,*sequences) 更像starmap(func,izip_longest(sequences)) -i.e.元组在发送到func 时会被解包,而不是作为元组传递,这只是一个小问题。
  • 没错,这就是我说“概念上”的原因(~=“就传递的数据而言,而不是传递的方式”)。不过我没想到starmap,这也是一个很好的例子!
猜你喜欢
  • 1970-01-01
  • 2021-10-08
  • 1970-01-01
  • 2016-01-21
  • 1970-01-01
  • 2011-03-15
  • 2020-04-23
  • 2023-01-13
  • 2021-07-05
相关资源
最近更新 更多