"iterating" 对单个项目的 pythonic 方式是什么?

What is the pythonic way of "iterating" over a single item?

我经常遇到这个问题,如果没有一些非常简单和 pythonic 的单行解决方案,我会感到惊讶。

假设我有一个方法或函数将列表或其他一些可迭代对象作为参数。我希望对对象中的每个项目执行一次操作。

有时,只有一个项目(例如,一个浮点值)被传递给这个函数。在这种情况下,我的 for 循环不知道该怎么办。因此,我发现自己在代码中添加了以下代码片段:

from collections.abc import Sequence

def my_function(value):
   if not isinstance(value, Sequence):
      value = [value]

   # rest of my function

这行得通,但看起来很浪费,而且不是特别清晰。在搜索 Whosebug 时,我还发现字符串被视为序列,因此如果参数错误,这段代码很容易中断。感觉这不是正确的方法。

我有 MATLAB 背景,这在该语言中得到了巧妙的解决,因为标量被视为 1x1 矩阵。我希望,至少,有一个内置的东西,比如 numpy 的 atleast_1d function,如果它不是一个,它会自动将任何东西转换成一个可迭代的东西。

简短的回答是不,没有简单的built-in。是的,如果你想让 str(或 bytes 或 bytes-like 之类的东西)充当标量值,它会变得更丑陋。 Python 期望调用者遵守接口约定;如果你说你接受序列,那么就这么说吧,它由调用者来包装任何单独的参数。

如果您必须这样做,有两种显而易见的方法:

首先是让您的函数接受可变参数而不是单个参数,并将其留给调用者解压任何序列,这样您就可以始终迭代接收到的可变参数:

def my_function(*values):
    for val in values:
        # Rest of function

带有单个项目的来电者用 my_function(a, b) 呼叫您,带有序列的呼叫者用 my_function(*seq) 呼叫您。后者确实会产生一些开销以将序列解压缩为新的 tuple 以供 my_function 接收,但在许多情况下这很好。

如果出于某种原因这不可接受,另一种解决方案是按照您关心的任何规则推出您自己的“确保可迭代”转换器函数:

from collections.abc import ByteString

def ensure_iterable(obj):
    if isinstance(obj, (str, ByteString)):
        return (obj,)  # Treat strings and bytes-like stuff as scalars and wrap
    try:
        iter(obj)  # Simplest way to test if something is iterable is to try to make it an iterator
    except TypeError:
        return (obj,)  # Not iterable, wrap
    else:
        return obj  # Already iterable

my_function 可用于:

def my_function(value):
   value = ensure_iterable(value)

我会鸭式:

def first(item):
    try:
        it=iter(item)
    except TypeError:
        it=iter([item])
    return next(it) 

测试一下:

tests=[[1,2,3],'abc',1,1.23]

for e in tests:
    print(e, first(e))

打印:

[1, 2, 3] 1
abc a
1 1
1.23 1.23

Python 是一种通用语言,具有真正的标量,以及像列表这样的可迭代对象。

MATLAB 没有真正的标量。基础对象是一个二维矩阵。它最初并不是一种通用语言。

numpy 将类似 MATLAB 的数组添加到 Python,但它也可以有 0d 数组 (scalar arrays),这可能会让任性的 MATLAB 用户头疼。

许多 numpy 函数都有将其输入转换为数组的规定。这样他们就可以处理列表输入以及数组

In [10]: x = np.array(3)
In [11]: x
Out[11]: array(3)
In [12]: x.shape
Out[12]: ()
In [13]: for i in x: print(x)
Traceback (most recent call last):
  Input In [13] in <cell line: 1>
    for i in x: print(x)
TypeError: iteration over a 0-d array

它还有确保数组为 1d 或 2 ...

的实用函数
In [14]: x = np.atleast_1d(1)
In [15]: x
Out[15]: array([1])
In [16]: for i in x: print(i)
1

但是像 old-fashion MATLAB 一样,我们更愿意避免在 numpy 中进行迭代。它没有 jit 编译功能,无法让当前的 MATLAB 用户进行迭代。技术上 numpy 函数确实使用迭代,但它通常在编译代码中。

np.sin应用于各种输入:

In [17]: np.sin(1)          # scalar
Out[17]: 0.8414709848078965
In [18]: np.sin([1,2,3])    # list
Out[18]: array([0.84147098, 0.90929743, 0.14112001])
In [19]: np.sin(np.array([1,2,3]).reshape(3,1))
Out[19]: 
array([[0.84147098],
       [0.90929743],
       [0.14112001]])

从技术上讲,[17] 结果是 numpy scalar,而不是基数 python 浮点数:

In [20]: type(Out[17])
Out[20]: numpy.float64