有没有办法覆盖Python中任意对象的双下划线(魔术)方法?
我想编写一个包装器类,它接受一个值,除了添加一个'reason'属性外,其行为与之类似。我有这样的想法:有没有办法覆盖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
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