2014-09-26 38 views
9

我想重载字符串内建的一些方法。 我知道这没有真正的合法用例,但行为仍然让我感到不安,所以我想解释一下这里发生了什么:Python超载原语

使用Python2和forbiddenfruit模块。

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

正如你所看到的,__repr__功能已成功超载,但是当我们问一个表示实际上不叫。这是为什么?

然后,你会怎么做才能得到预期的行为:

>>> 'foo' 
'bar' 

没有关于建立一个自定义的环境中,如果重建蟒蛇是什么需要,就这样吧约束,但我真不不知道从哪里开始,我仍然希望有一个更简单的方法:)

+2

你试图解决什么问题(这使你想重载内置方法)? – goncalopp 2014-09-26 14:11:15

+0

可能重复的[重写对一个实例的特殊方法](http://stackoverflow.com/questions/10376604/overriding-special-methods-on-an-instance) – njzk2 2014-09-26 15:11:33

+0

@goncalopp:我想要做的是有一个正在运行的python shell,其中调用任何字符串的__repr__被我的自定义方法替换。它不必在qny程序中使用,它必须在任何其他python解释器上运行,所以如我所说,只要我知道如何做到这一点,对自己的python进行猴子修补就可以了。 – Centime 2014-09-26 16:00:47

回答

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'" 

因此,我们知道,有些anticlimactically ,还有其他事情正在发生。

这里的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中,它查找“缓存的”tp_repr属性。

Here's what happens当你设置像TYPE.__repr__ = new_repr的东西属性:

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类型本身。

如果你想这样做,最简单的尝试可能是(暂时?)在str类上设置type->tp_flags & Py_TPFLAGS_HEAPTYPE这将允许正常设置属性。 当然,不能保证这不会导致您的解释器崩溃。

这不是我想要做的(特别是不通过​​),除非我真的要,所以我给你提供一个捷径。

你写:

然后,你会怎么做才能获得预期的行为:使用sys.displayhook

>>> 'foo' 
'bar' 

这其实很简单:

sys.displayhook是要求评估在交互中输入的expression的结果Python会话。这些值的显示可以通过给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)速度仍然很有用。

+0

这个!谢谢你Veedrac!这是完美的,有很多解释和所有。我只是过了一半,但它比我期待的要多。 – Centime 2014-09-27 12:44:50

+0

它[不起作用](https://gist.github.com/metaperl/99103cdfbe675afeaa38564ebcea7288) – 2017-04-11 10:15:54

+0

@TerrenceBrannon事实上,我解释了为什么它不在这个答案中。这可能我还没有足够清楚,所以如果你能解释为什么你认为我已经说过了,否则它会帮助我纠正任何含糊之处。 – Veedrac 2017-04-11 10:24:51