这意味着 Python 中的整数是具有“值”字段的实际对象,用于保存整数的值。在 Java 中,您可以这样表达 Python 的整数(当然,省略很多细节):
class PyInteger {
private int value;
public PyInteger(int val) {
this.value = val;
}
public PyInteger __add__(PyInteger other) {
return new PyInteger(this.value + other.value);
}
}
为了不存在大量具有相同值的 Python 整数,它缓存了一些整数,如下所示:
PyInteger[] cache = {
new PyInteger(0),
new PyInteger(1),
new PyInteger(2),
...
}
但是,如果你做了这样的事情会发生什么(让我们暂时忽略value 是私有的):
PyInteger one = cache[1]; // the PyInteger representing 1
one.value = 3;
突然之间,每次你在程序中使用1,你实际上都会得到3,因为代表1的对象的有效值是3。
确实,您可以在 Python 中做到这一点!即:可以在 Python 中更改整数的有效数值。 this reddit post 有答案。不过,为了完整起见,我将其复制到此处(原始学分归 Veedrac):
import ctypes
def deref(addr, typ):
return ctypes.cast(addr, ctypes.POINTER(typ))
deref(id(29), ctypes.c_int)[6] = 100
#>>>
29
#>>> 100
29 ** 0.5
#>>> 10.0
Python 规范本身并没有说明如何在内部存储或表示整数。它也没有说明应该缓存哪些整数,或者根本就应该缓存任何整数。简而言之:Python 规范中没有任何内容定义如果你做了这样愚蠢的事情会发生什么;-)。
我们甚至可以更进一步......
实际上,上面的字段value 实际上是一个整数数组,模拟一个任意大的整数值(对于一个 64 位整数,您只需组合两个 32 位字段等)。但是,当整数开始变大并超过标准的 32 位整数时,缓存不再是可行的选择。即使您使用字典,比较整数数组是否相等也会带来太多开销而收益太少。
您实际上可以通过使用is 来比较身份来自己检查:
>>> 3 * 4 is 12
True
>>> 300 * 400 is 120000
False
>>> 300 * 400 == 120000
True
在典型的 Python 系统中,只有一个对象代表数字 12。另一方面,120000 几乎不会被缓存。因此,在上面,300 * 400 生成了一个新对象,表示 120000,这与为右侧数字创建的对象不同。
为什么这是相关的?如果更改1 或29 之类的小数字的值,它将影响使用该数字的所有计算。您很可能会严重破坏您的系统(直到您重新启动)。但是如果你改变一个大整数的值,影响就会很小。
将12 的值更改为13 意味着3 * 4 将产生13。将120000 的值更改为130000 的影响要小得多,300 * 400 仍会产生(新的)120000 而不是130000。
一旦考虑到其他 Python 实现,事情就会变得更加难以预测。例如,MicroPython 没有用于小数字的对象,而是动态计算它们,PyPy 很可能只是优化您的更改。
底线:您修补的数字的确切行为确实未定义,但取决于几个因素和确切的实现。
回答cmets中的一个问题:上面Veedrac的代码中6的意义是什么?
Python 中的所有对象共享一个共同的内存布局。第一个字段是一个引用计数器,它告诉您当前有多少其他对象正在引用该对象。第二个字段是对对象的class 或type 的引用。由于整数没有固定的大小,所以第三个字段是数据部分的大小(可以找到相关定义here (general objects)和here (integers/longs)):
struct longObject {
native_int ref_counter; // offset: +0 / +0
PyObject* type; // offset: +1 / +2
native_int size; // offset: +2 / +4
unsigned short value[]; // offset: +3 / +6
}
在 32 位系统上,native_int 和 PyObject* 都占用 32 位,而在 64 位系统上它们自然占用 64 位。因此,如果我们在 64 位系统上以 32 位(使用ctypes.c_int)访问数据,则整数的实际值将在偏移量+6 处找到。另一方面,如果将类型更改为ctypes.c_long,则偏移量为+3。
因为CPython中的id(x)返回的是x的内存地址,这个其实可以自己查。基于上面的deref函数,我们来做:
>>> deref(id(29), ctypes.c_long)[3]
29
>>> deref(id(29), ctypes.c_long)[1]
10277248
>>> id(int) # memory address of class "int"
10277248