Python的__radd__不适用于C定义的类型

时间:2013-09-13 19:51:28

标签: python python-2.7 built-in python-c-extension python-extensions

当创建一个使用noddy.Noddy方法定义__radd__类型的python(2.7.5)扩展时,它会获得与具有自定义{的(其他等效的)python定义类对象不同的行为{ {1}}(前者不起作用,后者起作用)。例如:

__radd__

和相应的输出:

class PythonClass():
    def __radd__(self, other):
        return 'indeed!'

w = PythonClass()
d = noddy.Noddy()

print(w.__radd__)
print(d.__radd__)

print('the following works:')
print([1] + w)
print('the following does not work:')
print([1] + d)

方法<bound method PythonClass.__radd__ of <__main__.PythonClass instance at 0xf6e9792c>> <built-in method __radd__ of noddy.Noddy object at 0xf749d4b8> the following works: indeed! the following does not work: Traceback (most recent call last): File "examples/2.py", line 44, in <module> print([1] + d) TypeError: can only concatenate list (not "noddy.Noddy") to list 未调用,但d.__radd__是。关于为什么会这样的任何想法? w.__radd__ [1] + x x实例PythonClass的行为似乎与documentaton一致,我希望noddy.Noddy也能正常工作。此外,两者都是与list无关的类型。

欢迎解决方法。我已经尝试用forbiddenfruit修补list.__radd__,虽然没有成功,但我已将此问题提请作者注意,他恰好是我的好朋友。

修改

......这是C地的照片:

typedef struct {
    PyObject_HEAD
} Noddy;


static PyObject*
Noddy_radd(PyObject* _self, PyObject* args) {
    printf("Noddy_radd!\n");
    return NULL;
}

static PyObject*
Noddy_add(PyObject* _self, PyObject* args) {
  printf("Noddy_add\n");
  return NULL;
}

PyNumberMethods noddy_nums = {
  Noddy_add,         /* binaryfunc nb_add;         /* __add__ */
    0,               /* binaryfunc nb_subtract;    /* __sub__ */
    0,               /* binaryfunc nb_multiply;    /* __mul__ */
    0,               /* binaryfunc nb_divide;      /* __div__ */
    0,               /* binaryfunc nb_remainder;   /* __mod__ */
    0,               /* binaryfunc nb_divmod;      /* __divmod__ */
    0,               /* ternaryfunc nb_power;      /* __pow__ */
    0,               /* unaryfunc nb_negative;     /* __neg__ */
    0,               /* unaryfunc nb_positive;     /* __pos__ */
    0,               /* unaryfunc nb_absolute;     /* __abs__ */
    0,               /* inquiry nb_nonzero;        /* __nonzero__ */
    0,               /* unaryfunc nb_invert;       /* __invert__ */
    0,               /* binaryfunc nb_lshift;      /* __lshift__ */
    0,               /* binaryfunc nb_rshift;      /* __rshift__ */
    0,               /* binaryfunc nb_and;         /* __and__ */
    0,               /* binaryfunc nb_xor;         /* __xor__ */
    0,               /* binaryfunc nb_or;          /* __or__ */
    0,               /* coercion nb_coerce;        /* __coerce__ */
    0,               /* unaryfunc nb_int;          /* __int__ */
    0,               /* unaryfunc nb_long;         /* __long__ */
    0,               /* unaryfunc nb_float;        /* __float__ */
    0,               /* unaryfunc nb_oct;          /* __oct__ */
    0,               /* unaryfunc nb_hex;          /* __hex__ */
};

static PyMethodDef Noddy_methods[] = {
    {"__radd__", (PyCFunction)Noddy_radd, METH_VARARGS,
     "__radd__ function"},
    {NULL}  /* Sentinel */
};


static PyTypeObject NoddyType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "noddy.Noddy",             /*tp_name*/
    sizeof(Noddy),             /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    0,                         /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    &noddy_nums,               /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT |
      Py_TPFLAGS_HAVE_SEQUENCE_IN | /* tp_flags */
      Py_TPFLAGS_HAVE_ITER,
    "Noddy objects",           /* tp_doc */
    0,                     /* tp_traverse */
    0,                     /* tp_clear */
    0,                     /* tp_richcompare */
    0,                     /* tp_weaklistoffset */
    0,                     /* tp_iter */
    0,                     /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    0,                         /* tp_init */
    0,                         /* tp_alloc */
    PyType_GenericNew,         /* tp_new */
};

3 个答案:

答案 0 :(得分:1)

Python的getattr是一个棘手的家伙。 __radd__方法是[in]着名魔术方法的一部分。它们不与常规方法(ob_type->tp_methods)存储在同一个数组中,它是ob_type->tp_as_number的一部分,由Number Protocol单独管理。

禁果有一个问题,要求能够修补那些方法。这项工作是documented here

答案 1 :(得分:0)

Python Docs,查看footer

  

对于相同类型的操作数,假设如果非反射方法(例如 add ())失败,则不支持该操作,这就是为什么不调用反射方法的原因

答案 2 :(得分:0)

您是如何实施__radd__的?

对于C扩展,未明确定义__radd__。有一个名为nb_add的插槽支持指向接受两个参数的函数的指针。在Python类方法中,第一个参数始终是实例(即self)。因此,需要正常和反射方法。对于C扩展,情况并非如此。可以使用实例作为参数调用nb_add

修改

如果将Noddy_add的签名重写为Noddy_add(PyObject* a, PyObject* b),可能会更容易理解。设d是自定义C类型的实例。然后按如下方式处理[1] + d(忽略滥用语法和一些特殊情况):

调用

PyNumber_Add([1], d)。它首先尝试ListType.nb_add([1], d)失败,因为ListType没有实现nb_add。然后它尝试NoddyType.nb_add([1], d),这是你想要处理的调用。如果此调用失败,则调用ListType.sq_concat([1], d)

评估d + [1]时,相同的序列以NoddyType.nb_add(d, [1])结尾。除非您为NoddyType实现序列方法,否则不会调用ListType.sq_concat

您需要修改Noddy_add,以便可以通过引用列表作为第一个参数并将引用NoddyType作为第二个参数来调用它。使用反转的参数调用nb_add等同于Python代码中的__radd__

有关详细信息,请参阅Objects / abstract.c中的PyNumber_Add