我正在尝试重载内置字符串的某些方法。
我知道没有真正合法的用例,但是这种行为仍然困扰着我,因此我想对这里发生的事情进行解释:

使用Python2和forbiddenfruit模块。

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

如您所见,__repr__函数已成功重载,但是当我们要求一个表示形式时实际上并未被调用。这是为什么?

然后,您将如何做才能获得预期的行为:
>>> 'foo'
'bar'

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

最佳答案

首先要注意的是,无论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中查找,而是在“cached” 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进行的操作),因此为您提供了一条捷径。

    你写:



    使用 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)仍然很有用。

    关于Python重载原语,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26061351/

    10-16 03:17