1 + 1在Python中可以等于3吗?

时间:2018-12-27 19:55:26

标签: python python-3.x internals

在进行任何进一步的操作之前,我知道应该永远。这个问题纯粹是出于教育目的。我进行此练习是为了更好地了解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”是如此重要,以至于在不对我的解释器进行分段的情况下无法进行这项工作?

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