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数组是一个可变对象。
答案 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 Reference,3.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)
的内容已更改--- data
和x
仍然是一个而且只有对象。从我上面链接的文档:
这些方法应该尝试就地执行操作(修改
data
)并返回结果(可能是,但不一定是self
)。如果未定义特定方法,则增强赋值将回退到常规方法[意为self
和族--- KJC ]。
如果你看一下不同数据类型的方法,你会发现它们并不支持所有这些。
__add__
表明整数缺少就地方法,这不应该令人惊讶,因为它们是不可变的。
dir(0)
只显示两种就地方法:dir([])
和__iadd__
---您无法从列表中划分或减去,但您可以在-place添加另一个列表,您可以将它乘以整数。 (同样,这些方法可以用他们的论点做任何他们想做的事情,包括拒绝他们...... __imul__
不会取整数,而list.__iadd__
会拒绝列表。)
list.__imul__
基本上显示了算术,逻辑和按位方法的 all ,每个方法都是正常的和就地的。 (它可能会遗漏一些 - 我并没有全部计算它们。)
最后,重新重申一下,当调用方法时这些对象所处的命名空间绝对没有区别。在Python中,数据无范围 ...如果您有引用它,则可以在其上调用方法。 (来自Ned Batchelder的演讲:变量有一个范围,但没有类型;数据有类型,但没有范围。)