为什么Python变量每次被修改时都会占用一个新地址(id)?

时间:2013-02-22 12:30:18

标签: python

只是想知道这个背后的逻辑是什么?从表面上看,它似乎效率低下,每当你做一些像“x = x + 1”这样简单的事情时,它必须采用一个新地址并丢弃旧地址。

5 个答案:

答案 0 :(得分:8)

Python变量(在Python中称为标识符或名称)是对值的引用。 id()函数表示该值的某些内容,而不是名称。

许多价值观都不可变;整数,字符串,浮点数都 not 更改到位。将1添加到另一个整数时,将返回 new 整数,然后将该引用替换为旧值。

您可以将Python名称视为标签,与值绑定。如果您将值想象为气球,那么每次分配给该名称时,您都会重新标记标签新气球。如果再没有其他标签贴在气球上,它就会在风中漂移,永远不会被再次看到。 id()函数为您提供该气球的唯一编号。

请参阅此previous answer of mine,我会更多地谈论价值观作为气球的想法。

这似乎效率低下。对于许多经常使用的 small 值,Python实际上使用了一个名为interning的进程,它会缓存这些值的存储以供重用。 None是一个值,小整数和空元组(())也是如此。您可以使用intern()函数对您希望大量使用的字符串执行相同的操作。

但请注意,只有当引用计数(“标签”数量)下降到0时才会清除值。值的值会一直重复使用,特别是那些实体整数和单例。

答案 1 :(得分:2)

因为基本类型是不可变的,所以每次修改它时都需要再次实例化

...这完全没问题,特别是thread-safe函数

答案 2 :(得分:2)

=运算符不会修改对象,它会将名称分配给完全不同的对象,该对象可能已经或可能没有ID。

对于您的示例,整数是不可变的;没有办法添加一个东西并保持相同的ID。

事实上,小整数至少在cPython中实现,所以如果你这样做:

x = 1
y = 2
x = x + 1

然后xy可能具有相同的ID。

答案 3 :(得分:1)

在python中,“原始”类型如int和字符串是不可变的,这意味着它们不能被修改。

Python实际上非常有效,因为, @Wooble 注释,«非常短的字符串和小整数被实习。»:如果两个变量引用相同的( em> small )不可变值它们的id是相同的(减少重复的不可变量)​​。

>>> a = 42
>>> b = 5
>>> id(a) == id(b)
False
>>> b += 37
>>> id(a) == id(b)
True

使用不可变类型的原因是对这些值的并发访问的安全方法。

在一天结束时,它取决于设计选择。

根据您的需要,您可以更多地利用实施而不是其他实施 例如,可以在类似的语言Ruby中找到不同的哲学,其中Python中的那些类型是不可变的,不是。

答案 4 :(得分:1)

准确地说,赋值x=x+1不会修改x引用的对象,只是让x指向另一个值为x+1的对象。

要理解背后的逻辑,需要理解value semantics和引用语义之间的区别。

具有值语义的对象意味着它的值很重要,而不是它的身份。虽然具有引用语义的对象关注其身份(在Python中,可以从id(obj)返回标识)。

通常,值语义意味着对象的不变性。或者相反,如果一个对象是可变的(即就地更改),那意味着它具有引用语义。

让我们简要解释这种不变性背后的基本原理。

具有引用语义的对象可以在就地中更改,而不会丢失其原始地址/身份。这是有道理的,因为它是具有引用语义的对象的标识,使其自身可以与其他对象区分开来。

相比之下,具有值语义的对象永远不会改变自己。

首先,这在理论上是可能和合理的。由于只有值(而不是其身份)是重要的,当需要进行更改时,将其交换为具有不同值的另一个身份是安全的。这称为referential transparency。请注意,对于具有引用语义的对象,这是不可能的。

其次,这在实践中是有益的。正如OP所想的那样,每次更改时丢弃旧对象似乎效率低下,但大多数时候它更有效率。首先,Python(或任何其他语言)具有内部/缓存方案,以减少要创建的对象。更重要的是,如果将价值语义的对象设计为可变的,那么在大多数情况下它会占用更多的空间。

例如,Date具有值语义。如果它被设计为可变的,那么从内部字段返回日期的任何方法都会将句柄暴露给外部世界,这是有风险的(例如,外部可以直接修改此内部字段而无需借助公共接口)。类似地,如果通过引用某个函数/方法传递任何日期对象,则可以在该函数/方法中修改此对象,这可能与预期的不同。为了避免这些副作用,必须做defensive programming:不是直接返回内部日期字段,而是返回它的克隆;而不是通过引用传递,他通过值,这意味着额外的副本。可以想象,创造更多物体的机会多于必要。更糟糕的是,这些额外的克隆使代码变得更加复杂。

总之,不变性强制执行价值语义,它通常涉及较少的对象创建,较少的副作用和较少的麻烦,并且更适合测试。此外,不可变对象本质上是线程安全的,这意味着在多线程环境中锁定更少,效率更高。

这就是为什么像数字,字符串,日期,时间这样的值语义的基本数据类型都是不可变的原因(好吧,C ++中的字符串是一个例外,这就是为什么要避免这么多const string&的东西字符串被意外修改)。作为一个教训,Java在设计价值语义类DatePointRectangleDimension时犯了错误。

众所周知,OOP中的对象有三个特征:状态,行为和身份。具有值语义的对象不是典型的对象,因为它们的身份根本不重要。通常它们是 passive ,主要用于描述其他真实活动对象(即具有引用语义的对象)。这是区分值语义和引用语义的一个很好的提示。