在进行任何进一步的操作之前,我知道应该永远。这个问题纯粹是出于教育目的。我进行此练习是为了更好地了解python的内部,ctypes
及其工作方式。
我知道在python中更改整数值相对容易。实际上,有一个whole lot you can do是通过弄乱内部结构来实现的。在C API reference中,
当前实现为所有对象保留一个整数对象数组 在-5到256之间的整数,当您在该范围内创建int时, 实际上只是获取对现有对象的引用。所以 应该可以更改1的值。我怀疑该行为 在这种情况下,Python的定义是不确定的。 :-)
考虑到值1是由CPython缓存的,这样做应该相对容易(或至少可能)。经过一番摸索,我发现ctypes
是行之有效的方法。但是,我尝试的大多数结果都会导致段错误。我通过更改2的值来接近。
import ctypes
def deref(addr, typ):
return ctypes.cast(addr, ctypes.POINTER(typ))
deref(id(2), ctypes.c_int)[6] = 1
1 + 1现在给出错误的结果(朝正确方向迈出的一步),但我无法将其评估为“ 3”:
>>> 1 + 1
1
>>> 1 + 2
1
>>> 1 + 3
[1] 61014 segmentation fault python3.6
我尝试过类似的事情,但由于abarnert的internals
模块而失败。有什么方法可以让python中的1 + 1
评估为3
?还是“ 1”是如此重要,以至于在不对我的解释器进行分段的情况下无法进行这项工作?
答案 0 :(得分:2)
免责声明:此答案仅涉及CPython;我可能也错过了问题的重点...
我能够通过用C编写Python扩展来实现这一点。
在Objects/intobject.c
中有一个信息结构PyInt_Type
。其tp_as_number
字段是一个运算符函数表,其中nb_add
字段是加法运算符:
// the function in the same file that nb_add points to
static PyObject *
int_add(PyIntObject *v, PyIntObject *w)
...
PyInt_Type
是公开的全局变量,可以在Unix中使用dlsym
在WinAPI中使用GetProcAddress
进行检索:
#include <dlfcn.h>
...
// symbol look-up from the Python extension
void* addr = dlsym(RTLD_DEFAULT, "PyInt_Type");
// pointer to PyInt_Type
PyTypeObject *int_type = addr;
// pointer to int_as_number (PyInt_Type.tp_as_number)
PyNumberMethods *int_funcs = int_type->tp_as_number;
// pointer to int_add (tp_as_number->nb_add)
int_add_orig = int_funcs->nb_add;
// override this with a custom function
int_funcs->nb_add = (binaryfunc)int_add_new;
...
// custom add function
PyObject *int_add_new(PyIntObject *v, PyIntObject *w)
{
long a = PyInt_AS_LONG(v);
long b = PyInt_AS_LONG(w);
// 1 + 1 = 3 special case
if (a == 1 && b == 1) {
return PyInt_FromLong(3);
}
// for all other cases default to the
// original add function which was retrieved earlier
return int_add_orig((PyObject *)v, (PyObject *)w);
}
通过保留所有原始代码和内部变量,新代码避免了以前遇到的段错误:
>>> # load the extension
>>> import [...]
>>> 1 + 1
2
>>> # call the extension function which overloads the add operator
>>> 1 + 1
3
>>> 1 + 0
1
>>> 1 + 2
3
>>> 1 + 3
4