Numpy干扰命名空间

时间:2016-06-17 08:11:46

标签: python numpy matplotlib namespaces

import numpy as np

def f(x):
    x /= 10

data = np.linspace(0, 1, 5)
print data
f(data)
print data

我的系统输出(debian 8,Python 2.7.9-1,numpy 1:1.8.2-2)

[ 0. 0.25  0.5   0.75  1.  ]
[ 0. 0.025  0.05   0.075  0.1  ]

通常情况下,我希望data在传递给函数时保持不变,因为它有自己独立的命名空间。但是当数据是一个numpy数组时,函数会全局更改data

这是一个功能,一个错误还是我可能错过了一些东西?使用自定义绘图功能自动缩放数据时,应该如何避免这种行为?

更新请参阅Kevin J. Chase的答案了解更多详情

import numpy as np

def f(x):
    print id(x)
    x = x/10
    print id(x)

data = np.linspace(0, 1, 5)
print id(data)
print data
f(data)
print data

我的系统输出(debian 8,Python 2.7.9-1,numpy 1:1.8.2-2)

48844592
[ 0. 0.25  0.5   0.75  1.  ]
48844592
45972592
[ 0. 0.25  0.5   0.75  1.  ]

使用x = x/10代替x /= 10解决了我的问题。

nice和short x /= 10语句的行为实际上很大程度上取决于x 的类型。如果x是不可变的,它会重新绑定,否则会发生变异。

它不等同于总是重新绑定的x = x/10

numpy数组是一个可变对象。

1 个答案:

答案 0 :(得分:3)

  

通常情况下,我希望数据在传递给函数时保持不变,因为它有自己独立的命名空间。

函数中的

x和模块级别的data相同对象的两个名称。由于该对象是可变的,因此对其进行的任何更改都将被视为"无论使用哪个名称来引用该对象。命名空间无法保护您。

x /= 10将NumPy数组的每个元素除以10.原始数据在此行执行后消失。如果您再次运行f(data)几次,则每次都会发现内容越接近0.0。

列表是一个更为常见的相同效果的例子:

l = list(range(4))
print(l)
# [0, 1, 2, 3]
l += [4]
print(l)
# [0, 1, 2, 3, 4]

为了更好地了解这类事情(包括相关问题),我推荐Ned Batchelder的“Facts and Myths about Python Names and Values”(26分钟video from PyCon US 2015)。列表的例子"添加"开始大约10分钟。

幕后花絮

//=(和类似的运算符对)做不同的事情。教程经常声称这两个操作是相同的:

x = x / 10
x /= 10

......但他们并非如此。有关详细信息,请参阅The Python Language Reference3.3.7. Emulating Numeric Types

/在两个对象之一上调用__truediv__(或者可能是__rtruediv__ ---另一天的主题)方法,将另一个对象作为参数:

# x = x / 10
x = x.__truediv__(10)

通常,这些方法会返回一些新值而不会更改旧值。这就是data x / 10未改变的原因,但id(x)已更改--- x现在称为新对象,而不再是{{1}的别名}。

data调用一种完全不同的方法,/=用于"就地"操作:

__itruediv__

这些方法通常会修改对象,然后返回# x /= 10 x = x.__itruediv__(10) 。这解释了为什么self未更改为什么id(x)的内容已更改--- datax仍然是一个而且只有对象。从我上面链接的文档:

  

这些方法应该尝试就地执行操作(修改data)并返回结果(可能是,但不一定是self)。如果未定义特定方法,则增强赋值将回退到常规方法[意为self和族--- KJC ]。

如果你看一下不同数据类型的方法,你会发现它们并不支持所有这些。

  • __add__表明整数缺少就地方法,这不应该令人惊讶,因为它们是不可变的。

  • dir(0)只显示两种就地方法:dir([])__iadd__ ---您无法从列表中划分或减去,但您可以在-place添加另一个列表,您可以将它乘以整数。 (同样,这些方法可以用他们的论点做任何他们想做的事情,包括拒绝他们...... __imul__不会取整数,而list.__iadd__会拒绝列表。)

  • list.__imul__基本上显示了算术,逻辑和按位方法的 all ,每个方法都是正常的和就地的。 (它可能会遗漏一些 - 我并没有全部计算它们。)

最后,重新重申一下,当调用方法时这些对象所处的命名空间绝对没有区别。在Python中,数据无范围 ...如果您有引用它,则可以在其上调用方法。 (来自Ned Batchelder的演讲:变量有一个范围,但没有类型;数据有类型,但没有范围。)