有没有办法覆盖Python中任意对象的双下划线(魔术)方法?

有没有办法覆盖Python中任意对象的双下划线(魔术)方法?,python,object,magic-methods,getattr,getattribute,Python,Object,Magic Methods,Getattr,Getattribute,我想编写一个包装器类,它接受一个值,除了添加一个'reason'属性外,其行为与之类似。我有这样的想法: class ExplainedValue(object): def __init__(self, value, reason): self.value = value self.reason = reason def __getattribute__(self, name): print '__getattribute__ w

我想编写一个包装器类,它接受一个值,除了添加一个'reason'属性外,其行为与之类似。我有这样的想法:

class ExplainedValue(object):
    def __init__(self, value, reason):
        self.value = value
        self.reason = reason

    def __getattribute__(self, name):
        print '__getattribute__ with %s called' % (name,)
        if name in ('__str__', '__repr__', 'reason', 'value'):
            return object.__getattribute__(self, name)
        value = object.__getattribute__(self, 'value')
        return object.__getattribute__(value, name)

    def __str__(self):
        return "ExplainedValue(%s, %s)" % (
            str(self.value),
            self.reason)
    __repr__ = __str__
但是,使用
\uuuu getattribute\uuuu
似乎无法捕获双下划线函数,例如:

>>> numbers = ExplainedValue([1, 2, 3, 4], "it worked")
>>> numbers[0]

Traceback (most recent call last):
  File "<pyshell#118>", line 1, in <module>
    numbers[0]
TypeError: 'ExplainedValue' object does not support indexing
>>> list(numbers)
__getattribute__ with __class__ called

Traceback (most recent call last):
  File "<pyshell#119>", line 1, in <module>
    list(numbers)
TypeError: 'ExplainedValue' object is not iterable

为什么没有发生这种情况?我怎样才能做到?(在真实代码中实际使用这可能是一个可怕的想法,但我现在对技术问题很好奇。)

正如millimoose所说,隐式的
\uuuuuuufoo\uuuu
调用永远不会通过
\uuuuGetAttribute\uuuuuu
。您唯一能做的就是向包装器类添加适当的函数

class Wrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped

    for dunder in ('__add__', '__sub__', '__len__', ...):
        locals()[dunder] = lambda self, __f=dunder, *args, **kwargs: getattr(self.wrapped, __f)(*args, **kwargs)

obj = [1,2,3]
w = Wrapper(obj)
print len(w)
类主体像任何其他块一样执行代码(除了
def
);你可以在里面放循环和任何你想要的东西。它们的神奇之处在于,整个局部范围被传递到块末尾的
type()
,以创建类


这可能是唯一一种分配给
局部变量()
甚至有点用处的情况。

为了子孙后代,我想到了以下几点:

class BaseExplainedValue(object):
    def __init__(self, value, reason):
        self.value = value
        self.reason = reason

    def __getattribute__(self, name):
        if name in ('value', 'reason'):
            return object.__getattribute__(self, name)
        value = object.__getattribute__(self, 'value')
        return object.__getattribute__(value, name)

    def __str__(self):
        return "<'%s' explained by '%s'>" % (
            str(self.value),
            str(self.reason))
    def __unicode__(self):
        return u"<'%s' explained by '%s'>" % (
            unicode(self.value),
            unicode(self.reason))
    def __repr__(self):
        return "ExplainedValue(%s, %s)" % (
            repr(self.value),
            repr(self.reason))

force_special_methods = set(
    "__%s__" % name for name in (
        'lt le eq ne gt ge cmp rcmp nonzero call len getitem setitem delitem iter reversed contains getslice setslice delslice' + \
        'add sub mul floordiv mod divmod pow lshift rshift and xor or div truediv' + \
        'radd rsub rmul rdiv rtruediv rfloordiv rmod rdivmod rpow rlshift rrshift rand rxor ror' + \
        'iadd isub imul idiv itruediv ifloordiv imod ipow ilshift irshift iand ixor ior' + \
        'neg pos abs invert complex int long float oct hex index coerce' + \
        'enter exit').split(),
)

def make_special_method_wrapper(method_name):
    def wrapper(self, *args, **kwargs):
        return getattr(self, method_name)(*args, **kwargs)
    wrapper.__name__ = method_name
    return wrapper

def EXP(obj, reason="no reason provided"):
    if isinstance(obj, BaseExplainedValue):
        return obj

    class ThisExplainedValue(BaseExplainedValue):
        pass
    #special-case the 'special' (underscore) methods we want
    obj_class = obj.__class__
    for method_name in dir(obj_class):
        if not (method_name.startswith("__") and method_name.endswith("__")): continue
        method = getattr(obj_class, method_name)
        if method_name in force_special_methods:
            setattr(ThisExplainedValue, method_name, make_special_method_wrapper(method_name))

    ThisExplainedValue.__name__ = "%sExplainedValue" % (obj_class.__name__,)
    return ThisExplainedValue(obj, reason)
所解释的值可与它们包装的值互换使用:

>>> numbers = EXP([1, 2, 3, 4, 5], "method worked ok")
>>> numbers
ExplainedValue([1, 2, 3, 4, 5], 'method worked ok')
>>> numbers[3]
4
>>> del numbers[3]
>>> numbers
ExplainedValue([1, 2, 3, 5], 'method worked ok')
它甚至愚弄了
isinstance
(解释):


我认为这是一种优化。我似乎模模糊糊地回忆起在Python源代码中,支持对象(或者可能是类型)的解释器的C结构具有用于
\uuuuuu magic\uuu
函数的特殊字段。这些是在创建对象(或类)时设置的。每当解释器内部需要调用这些函数时,它都可以直接调用它,而不是通过
getattr
机制进行查找。(显然,您应该通过下载源代码并查找此结构的定义位置来验证这一点。)如果您只想设置一个附加属性,则不需要包装类,您可以直接在原始对象上设置该属性:
original\u object.reason=“因为”
。但你可能已经知道这一点了……事实上,直接从马口中了解到:-这种行为及其背后的原理是有记录的,它不仅仅是一种优化。(关于
\uuuu getattribute\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu()的文档也链接到了这个。)@Nick我想问题的关键是为什么在查找
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,您可以编写一个元类或工厂函数,根据要包装的对象的类型自动创建必要的方法。这可能太神奇了,但请注意,对于像
\uuuuuuuu hash\uuuuuu
这样的方法来说,这是一个好主意,Python会根据它是否存在来做一些特殊的事情。(但也许你不应该代理
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu!我看到了一些python神奇的巫毒精灵灰尘的背后,我对我发现的东西不太满意=(.我会活下去的,虽然python神奇的巫毒精灵灰尘的美丽之处在于,除了语法之外,它只是稍微有点神奇,你可以在python本身中重新创建它:)
>>> success = EXP(True, "it went ok")
>>> if success:
        print 'we did it!'


we did it!
>>> success = EXP(False, "Server was on fire")
>>> if not success:
        print "We failed: %s" % (EXP(success).reason,)


We failed: Server was on fire
>>> numbers = EXP([1, 2, 3, 4, 5], "method worked ok")
>>> numbers
ExplainedValue([1, 2, 3, 4, 5], 'method worked ok')
>>> numbers[3]
4
>>> del numbers[3]
>>> numbers
ExplainedValue([1, 2, 3, 5], 'method worked ok')
>>> isinstance(EXP(False), bool)
True
>>> isinstance(EXP([]), list)
True