脾气暴躁的命名空间

费利克斯
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和shortx /= 10语句的行为实际上很大程度上取决于x类型如果x是不可变的,它将重新绑定,否则进行变异。

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

numpy数组是可变对象。

凯文·蔡斯(Kevin J.Chase)

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

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的“关于Python名称和值的事实和神话”(PyCon US 2015的26分钟视频)。他的“添加”列表示例开始于大约10分钟。

幕后花絮

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

x = x / 10
x /= 10

...但事实并非如此。全部细节可以发现Python语言参考3.3.7。模拟数值类型

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

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

通常,这些方法在不更改旧值的情况下返回一些新值。这就是为什么data未更改x / 10但被id(x)更改的原因---x现在引用了一个新对象,并且不再是的别名data

/=__itruediv__为“就地”操作调用完全不同的方法

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

这些方法通常会修改对象,然后返回self这就解释了为什么id(x)没有改变为什么data的内容发生了变化---xdata仍是唯一对象。从我上面链接的文档中:

这些方法应尝试就地进行操作(modify self)并返回结果(可以是,但不一定是self)。如果未定义特定方法,则扩展分配将退回到常规方法[含义__add__和族--- KJC ]。

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

  • dir(0) 表明整数缺少就位方法,这并不奇怪,因为它们是不可变的。

  • dir([])仅显示两个就地方法:__iadd____imul__---您不能从列表中除或减去,但可以就地添加另一个列表,并且可以将其乘以整数。(同样,这些方法可以对参数做任何想做的事情,包括拒绝它们……list.__iadd__不会取整数,而list.__imul__会拒绝列表。)

  • dir(np.linspace(0, 1, 5))基本上显示了所有的算术,逻辑和按位方法,每种方法都具有常规和就地方法。(可能会缺少一些---我没有全部算出来。)

最后,再次重申,这些对象在调用其方法时所处的命名空间完全没有区别。在Python中,数据没有作用域...如果您有对它的引用,则可以在其上调用方法。(摘自Ned Batchelder的演讲:变量具有范围,但没有类型;数据具有类型,但没有范围。)

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章