22 python implementation cpython
请参阅此链接:https : //docs.python.org/3/c-api/long.html#c.PyLong_FromLong
当前的实现为 -5 到 256 之间的所有整数保留了一个整数对象数组;当您在该范围内创建一个 int 时,您实际上只是取回了对现有对象的引用。所以,应该可以改变 1 的值。我怀疑 Python 的行为,在这种情况下,是 undefined。:-)
在这种情况下,粗线是什么意思?
Tob*_*ias 33
这意味着 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);
}
}
Run Code Online (Sandbox Code Playgroud)
为了避免大量具有相同值的 Python 整数,它会缓存一些整数,如下所示:
PyInteger[] cache = {
new PyInteger(0),
new PyInteger(1),
new PyInteger(2),
...
}
Run Code Online (Sandbox Code Playgroud)
但是,如果你做这样的事情会发生什么(让我们value暂时忽略它是私有的):
PyInteger one = cache[1]; // the PyInteger representing 1
one.value = 3;
Run Code Online (Sandbox Code Playgroud)
突然之间,每次1在程序中使用时,您实际上都会返回3,因为表示的对象1的有效值为3。
事实上,你可以在 Python 中做到这一点!也就是说:可以在 Python 中更改整数的有效数值。在这篇 reddit 帖子中有一个答案。不过,为了完整起见,我将其复制到此处(原始学分转到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
Run Code Online (Sandbox Code Playgroud)
Python 规范本身并没有说明如何在内部存储或表示整数。它也没有说明应该缓存哪些整数,或者应该缓存任何整数。简而言之:Python 规范中没有任何内容定义如果你做这样愚蠢的事情会发生什么;-)。
我们甚至可以走得更远......
实际上,value上面的字段实际上是一个整数数组,模拟任意大整数值(对于 64 位整数,您只需组合两个 32 位字段等)。然而,当整数开始变大并超过标准的 32 位整数时,缓存不再是一个可行的选择。即使你使用了字典,比较整数数组的相等性也会带来太多的开销而收益太少。
您实际上可以通过使用is来比较身份来自己检查:
>>> 3 * 4 is 12
True
>>> 300 * 400 is 120000
False
>>> 300 * 400 == 120000
True
Run Code Online (Sandbox Code Playgroud)
在典型的 Python 系统中,只有一个对象表示数字12。 120000,另一方面,几乎从未被缓存。因此,上面300 * 400产生了一个表示 的新对象120000,它不同于为右侧数字创建的对象。
为什么这是相关的?如果您更改一个小数字的值,例如1或29,它将影响使用该数字的所有计算。您很可能会严重破坏您的系统(直到您重新启动)。但是如果你改变一个大整数的值,影响会很小。
更改12to的值13意味着3 * 4将产生13。Chaning的价值120000,以130000有效果少得多,并300 * 400仍然会产生(新的)120000,而不是130000。
一旦你考虑到其他 Python 实现,事情就会变得更加难以预测。 例如,MicroPython没有用于小数的对象,但会动态处理它们,而PyPy可能只是优化您的更改。
底线:您修补的数字的确切行为确实未定义,但取决于几个因素和确切的实现。
回答评论中的一个问题:6上面 Veedrac 代码中的意义是什么?
Python 中的所有对象共享一个共同的内存布局。第一个字段是一个引用计数器,它告诉您当前有多少其他对象正在引用此对象。第二个字段是对对象的类或类型的引用。由于整数没有固定大小,因此第三个字段是数据部分的大小(您可以在此处(通用对象)和此处(整数/长整数)找到相关定义):
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
}
Run Code Online (Sandbox Code Playgroud)
在32位的系统,native_int和PyObject*既占用32位,而在64位的系统上它们占据64位,自然。因此,如果我们ctypes.c_int在 64 位系统上以 32 位(使用)访问数据,则可以在 offset 处找到整数的实际值+6。ctypes.c_long另一方面,如果将类型更改为,则偏移量为+3。
因为id(x)在 CPython 中返回 的内存地址x,你实际上可以自己检查一下。基于上面的deref函数,让我们做:
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
}
Run Code Online (Sandbox Code Playgroud)