Python过载原语

时间:2014-09-26 13:57:11

标签: python overloading

我正在尝试重载字符串内置的一些方法。 我知道没有真正合法的用例,但这种行为仍然让我感到烦恼,所以我想得到一个解释这里发生的事情:

使用Python2和forbiddenfruit模块。

>>> from forbiddenfruit import curse
>>> curse(str, '__repr__', lambda self:'bar')
>>> 'foo'
'foo'
>>> 'foo'.__repr__()
'bar'

正如您所看到的,__repr__函数已成功重载,但在我们要求表示时实际上并未调用。那是为什么?

然后,您将如何做到预期的行为:

>>> 'foo'
'bar'

没有关于设置自定义环境的限制,如果重建python是它所需要的,那就这样吧,但我真的不知道从哪里开始,我仍然希望有一种更简单的方法:)

1 个答案:

答案 0 :(得分:6)

首先要注意的是,无论forbiddenfruit做什么,它都不会影响repr。这不是str的特例,它不会像那样工作:

import forbiddenfruit

class X:
    repr = None

repr(X())
#>>> '<X object at 0x7f907acf4c18>'

forbiddenfruit.curse(X, "__repr__", lambda self: "I am X")

repr(X())
#>>> '<X object at 0x7f907acf4c50>'

X().__repr__()
#>>> 'I am X'

X.__repr__ = X.__repr__

repr(X())
#>>> 'I am X'

由于a much simpler way of doing what forbiddenfruit does的帖子,我最近找到了HYRY

import gc

underlying_dict = gc.get_referents(str.__dict__)[0]
underlying_dict["__repr__"] = lambda self: print("I am a str!")

"hello".__repr__()
#>>> I am a str!

repr("hello")
#>>> "'hello'"

所以,我们知道,有点讽刺的是,其他事情正在发生。

此处the source for builtin_repr

builtin_repr(PyModuleDef *module, PyObject *obj)
/*[clinic end generated code: output=988980120f39e2fa input=a2bca0f38a5a924d]*/
{
    return PyObject_Repr(obj);
}

对于PyObject_Repr(已省略的部分):

PyObject *
PyObject_Repr(PyObject *v)
{
    PyObject *res;
    res = (*v->ob_type->tp_repr)(v);
    if (res == NULL)
        return NULL;
}

重要的一点是,它不是在dict中查找,而是查找&#34;缓存&#34; tp_repr属性。

当您使用TYPE.__repr__ = new_repr

之类的内容设置属性时,

Here's what happens

static int
type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
{
    if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
        PyErr_Format(
            PyExc_TypeError,
            "can't set attributes of built-in/extension type '%s'",
            type->tp_name);
        return -1;
    }
    if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0)
        return -1;
    return update_slot(type, name);
}

第一部分是阻止你修改内置类型的东西。然后它通常设置属性(PyObject_GenericSetAttr),最重要的是,更新插槽。

如果您对其运作方式感兴趣,it's available here。关键点是:

  • 它不是导出的函数,

  • 它会修改PyTypeObject实例本身

如此复制它需要侵入PyTypeObject类型本身。

如果您想这样做,最简单的尝试可能是(暂时?)在type->tp_flags & Py_TPFLAGS_HEAPTYPE类上设置str这样可以设置属性一般。 当然,无法保证您的翻译不会崩溃。

除非我真的需要,否则这不是我想做的事情(尤其不是ctypes),所以我为你提供了一条捷径。

你写道:

  

然后,您将如何做到预期的行为:

>>> 'foo'
'bar'

使用sys.displayhook

实际上非常简单
  对评估在交互式Python会话中输入的expression的结果调用

sys.displayhook。可以通过为sys.displayhook指定另一个单参数函数来自定义这些值的显示。

以下是一个例子:

import sys

old_displayhook = sys.displayhook
def displayhook(object):
    if type(object) is str:
        old_displayhook('bar')
    else:
        old_displayhook(object)

sys.displayhook = displayhook

然后......(!)

'foo'
#>>> 'bar'

123
#>>> 123

关于为什么 repr将被缓存的哲学观点,首先要考虑:

1 + 1

如果在调用之前必须在字典中查找__add__,那将是一件痛苦的事情,CPython很慢,所以CPython决定将查找缓存到标准dunder(双下划线)方法。 __repr__就是其中之一,即使需要优化查找的情况也不常见。这对于快速格式化('%s'%s)仍然很有用。