Numpy 评估顺序

Numpy evaluation order

提问人:Martinghoul 提问时间:8/22/2023 最后编辑:jaredMartinghoul 更新时间:8/31/2023 访问量:90

问:

当我查看与 Numpy 索引如何工作相关的一些问题(有时是视图,有时是副本)时,我在 Numpy 文档中遇到了一个示例,这让我有点困惑。具体来说,可以在这里找到它。

很简单,我不明白以下片段的原因

import numpy as np

x = np.arange(2)
x[[0, 0]] += 1
print(x)

产生它所产生的结果。

我知道你得到你得到的原因是它似乎扩展到类似的东西,但我不明白为什么会这样(你可以测试一下,如果你对相关方法进行子类化和添加消息,就会发生这种情况)。x[[0, 0]] += 1x.setitem(x.getitem([0, 0]).iadd(1))ndarray

给定优先级规则,语句不是首先转换为 ,这意味着后续扩展应该看起来更像吗?我很理解这会很尴尬,但我并不真正理解产生允许修改原始数组的扩展的形式逻辑。x[[0, 0]] += 1x[[0, 0]] = x[[0, 0]] + 1x.getitem([0, 0]).setitem(x.getitem([0, 0]).iadd(1))

是否存在优先于订阅的隐式绑定?

我试过了

import numpy as np

class A(np.ndarray):
    def __getitem__(self, *args, **kwargs):
        print("getitem")
        r = np.ndarray.__getitem__(self, *args, **kwargs)
        return r
    def __setitem__(self, *args, **kwargs):
        print("setitem")
        r = np.ndarray.__setitem__(self, *args, **kwargs)
        return r 
    def __iadd__(self, *args, **kwargs):
        print("iadd")
        r = np.ndarray.__iadd__(self, *args, **kwargs)
        return r

nd = np.arange(2)
x = nd.view(A)
x[[0, 0]] += 1
print(x)
Python Numpy 就地

评论

1赞 hpaulj 8/22/2023
numpy.org/doc/stable/reference/generated/numpy.ufunc.at.html
0赞 hpaulj 8/22/2023
您是否仅对 [0,0] 情况感到困惑,还是对 [0,1] 选择感到困惑?
0赞 Martinghoul 8/22/2023
我对索引本身并不感到困惑,因为这是一个 numpy 实现细节。我对解释器用来产生结果的特定逻辑感到困惑。
0赞 hpaulj 8/23/2023
你第一次就做对了 - get、add、set。该方法包括必要的逻辑。在 期间,视图和副本之间的区别不适用。interpeter 所做的只是调用方法;实际工作是在那些特定于类的方法中,无论类是 Integer、List 还是 numpy 数组。__setitem___getset
0赞 Martinghoul 8/23/2023
我知道我做对了,因为我可以看到这就是发生的事情。但是,我不明白的是为什么!此外,view 和 copy 之间的区别在 期间不适用,但它在 期间适用,它决定了原始数组元素是递增一次还是多次递增(这在您有用链接到的 ufunc 的文档中也指出)。setget

答:

0赞 Martinghoul 8/23/2023 #1

就我而言,更多的挖掘使它更加令人费解。

我使用 ast 来查看解释器对以下块做了什么:

import numpy as np
x = np.arange(2)
x[[0, 0]] += 1

完整输出为:

("Module(body=[Import(names=[alias(name='numpy', asname='np')]), "
 "Assign(targets=[Name(id='x', ctx=Store())], "
 "value=Call(func=Attribute(value=Name(id='np', ctx=Load()), attr='arange', "
 'ctx=Load()), args=[Constant(value=2)], keywords=[])), '
 "AugAssign(target=Subscript(value=Name(id='x', ctx=Load()), "
 'slice=List(elts=[Constant(value=0), Constant(value=0)], ctx=Load()), '
 'ctx=Store()), op=Add(), value=Constant(value=1))], type_ignores=[])')

显示 的扩展的行,尽我所能格式化,是:x[[0,0]] += 1

AugAssign(
    target=Subscript(
            value=Name(id='x', ctx=Load()), 
            slice=List(elts=[Constant(value=0), Constant(value=0)], ctx=Load()),
            ctx=Store()
    ), 
    op=Add(), 
    value=Constant(value=1)
)

因此,基于此,AugAssign 的目标应该是 Subscript 操作的结果,那么它最终是如何实现的呢?这仍然没有意义......x.setitem(x.getitem([0, 0]).iadd(1))

评论

0赞 hpaulj 8/24/2023
我不知道 for 的细节(除了告诉我们的内容)。在 py2 中提出的 PEP203 总结道:AugAssignnumpyufunc.atThe object x is loaded, then y is added to it, and the resulting object is stored back in the original place. The precise action performed on the two arguments depends on the type of x, and possibly of y.
0赞 hpaulj 8/24/2023
这很重要。 适用于数字、列表和字符串(但不适用于元组),所有这些都定义了“+”操作。但细节不同。 数组的独特之处在于它们允许“重复”索引,例如 ,因此其他类型的行为很少给我们提示要扩展的内容。关键点是它不是作为循环实现的: .数组的“矢量化”行为是由它们自己的方法实现的,而不是由 python 解释器实现的。type of x+=numpy[0,0]+=for i in [0,0]: x[i]+=1
0赞 Martinghoul 8/30/2023 #2

我想我终于有了充分的了解......

Numpy文档描述了高级索引如何创建副本而不是视图,它指的是在引用的上下文中使用表达式(即在右侧)的情况。正如此处特别提到的同一文档,x[[1, 2]]

"...在分配期间,没有视图或副本是 创建...”x[[1, 2]]

虽然这有点有帮助,但没有给出推理。但是,可以在此处的基本赋值语句的 Python 参考文档中找到它。文档中描述的订阅和切片的特定逻辑解释了为什么在分配目标(位于左侧)时没有创建副本。x[[1, 2]]