python documentation表示在查找特殊方法时可能会绕过__getattribute__
。这是通过语言语法或内置函数进行隐式调用的结果。
例如,
elem = container[0]
与:
不同elem = container.__getattribute__('__getitem__')[0]
以下是另一个例子:
class WrappedList:
def __init__(self):
object.__setattr__(self, 'interal_list', ['apple', 'pear', 'orange'])
def __getattribute__(self, attr_name):
interal_list = object.__getattribute__(self, 'interal_list')
attr = getattr(interal_list, attr_name)
return attr
wl = WrappedList()
print("\nSTART TEST 01 ------------------------")
try:
print(wl[0]) # throws TypeError: 'WrappedList' object does not support indexing
except TypeError as e:
print(e)
print("\nSTART TEST 02 ------------------------")
try:
getitem = getattr(wl, '__getitem__')
print(getitem(0)) # works just fine
except TypeError as e:
print(e)
我想写一个名为MagicOverrider
的类,其中任何继承自MagicOverrider
的类总是调用__getattribute__
,而不是绕过它。我的问题是我们怎么做?
我尝试了以下内容:
class MagicOverrider:
def __call__(self, *args, **kwargs):
f = getattr(self, '__call__')
return f(*args, **kwargs)
def __iter__(self, *args, **kwargs):
f = getattr(self, '__iter__')
return f(*args, **kwargs)
def __getitem__(self, *args, **kwargs):
f = getattr(self, '__getitem__')
return f(*args, **kwargs)
def __setitem__(self, *args, **kwargs):
f = getattr(self, '__setitem__')
return f(*args, **kwargs)
def __add__(self, *args, **kwargs):
f = getattr(self, '__add__')
return f(*args, **kwargs)
def __sub__(self, *args, **kwargs):
f = getattr(self, '__sub__')
return f(*args, **kwargs)
def __mul__(self, *args, **kwargs):
f = getattr(self, '__mul__')
return f(*args, **kwargs)
def __truediv__(self, *args, **kwargs):
f = getattr(self, '__truediv__')
return f(*args, **kwargs)
def __floordiv__(self, *args, **kwargs):
f = getattr(self, '__floordiv__')
return f(*args, **kwargs)
def __mod__(self, *args, **kwargs):
f = getattr(self, '__mod__')
return f(*args, **kwargs)
def __divmod__(self, *args, **kwargs):
f = getattr(self, '__divmod__')
return f(*args, **kwargs)
def __pow__(self, *args, **kwargs):
f = getattr(self, '__pow__')
return f(*args, **kwargs)
def __lshift__(self, *args, **kwargs):
f = getattr(self, '__lshift__')
return f(*args, **kwargs)
def __rshift__(self, *args, **kwargs):
f = getattr(self, '__rshift__')
return f(*args, **kwargs)
def __and__(self, *args, **kwargs):
f = getattr(self, '__and__')
return f(*args, **kwargs)
def __xor__(self, *args, **kwargs):
f = getattr(self, '__xor__')
return f(*args, **kwargs)
def __or__(self, *args, **kwargs):
f = getattr(self, '__or__')
return f(*args, **kwargs)
def __radd__(self, *args, **kwargs):
f = getattr(self, '__radd__')
return f(*args, **kwargs)
def __rsub__(self, *args, **kwargs):
f = getattr(self, '__rsub__')
return f(*args, **kwargs)
def __rmul__(self, *args, **kwargs):
f = getattr(self, '__rmul__')
return f(*args, **kwargs)
def __rtruediv__(self, *args, **kwargs):
f = getattr(self, '__rtruediv__')
return f(*args, **kwargs)
def __rfloordiv__(self, *args, **kwargs):
f = getattr(self, '__rfloordiv__')
return f(*args, **kwargs)
def __rmod__(self, *args, **kwargs):
f = getattr(self, '__rmod__')
return f(*args, **kwargs)
def __rdivmod__(self, *args, **kwargs):
f = getattr(self, '__rdivmod__')
return f(*args, **kwargs)
def __rpow__(self, *args, **kwargs):
f = getattr(self, '__rpow__')
return f(*args, **kwargs)
def __rlshift__(self, *args, **kwargs):
f = getattr(self, '__rlshift__')
return f(*args, **kwargs)
def __rrshift__(self, *args, **kwargs):
f = getattr(self, '__rrshift__')
return f(*args, **kwargs)
def __rand__(self, *args, **kwargs):
f = getattr(self, '__rand__')
return f(*args, **kwargs)
def __rxor__(self, *args, **kwargs):
f = getattr(self, '__rxor__')
return f(*args, **kwargs)
def __neg__(self, *args, **kwargs):
f = getattr(self, '__neg__')
return f(*args, **kwargs)
def __pos__(self, *args, **kwargs):
f = getattr(self, '__pos__')
return f(*args, **kwargs)
def __abs__(self, *args, **kwargs):
f = getattr(self, '__abs__')
return f(*args, **kwargs)
def __invert__(self, *args, **kwargs):
f = getattr(self, '__invert__')
return f(*args, **kwargs)
def __complex__(self, *args, **kwargs):
f = getattr(self, '__complex__')
return f(*args, **kwargs)
def __int__(self, *args, **kwargs):
f = getattr(self, '__int__')
return f(*args, **kwargs)
def __float__(self, *args, **kwargs):
f = getattr(self, '__float__')
return f(*args, **kwargs)
def __round__(self, *args, **kwargs):
f = getattr(self, '__round__')
return f(*args, **kwargs)
def __index__(self, *args, **kwargs):
f = getattr(self, '__index__')
return f(*args, **kwargs)
def __eq__(self, *args, **kwargs):
f = getattr(self, '__eq__')
return f(*args, **kwargs)
def __ne__(self, *args, **kwargs):
f = getattr(self, '__ne__')
return f(*args, **kwargs)
def __lt__(self, *args, **kwargs):
f = getattr(self, '__lt__')
return f(*args, **kwargs)
def __le__(self, *args, **kwargs):
f = getattr(self, '__le__')
return f(*args, **kwargs)
def __gt__(self, *args, **kwargs):
f = getattr(self, '__gt__')
return f(*args, **kwargs)
def __ge__(self, *args, **kwargs):
f = getattr(self, '__ge__')
return f(*args, **kwargs)
def __bool__(self, *args, **kwargs):
f = getattr(self, '__bool__')
return f(*args, **kwargs)
def __new__(self, *args, **kwargs):
f = getattr(self, '__new__')
return f(*args, **kwargs)
def __del__(self, *args, **kwargs):
f = getattr(self, '__del__')
return f(*args, **kwargs)
def __slots__(self, *args, **kwargs):
f = getattr(self, '__slots__')
return f(*args, **kwargs)
def __hash__(self, *args, **kwargs):
f = getattr(self, '__hash__')
return f(*args, **kwargs)
def __instancecheck__(self, *args, **kwargs):
f = getattr(self, '__instancecheck__')
return f(*args, **kwargs)
def __subclasscheck__(self, *args, **kwargs):
f = getattr(self, '__subclasscheck__')
return f(*args, **kwargs)
def __subclasshook__(self, *args, **kwargs):
f = getattr(self, '__subclasshook__')
return f(*args, **kwargs)
def __ror__(self, *args, **kwargs):
f = getattr(self, '__ror__')
return f(*args, **kwargs)
def __iadd__(self, *args, **kwargs):
f = getattr(self, '__iadd__')
return f(*args, **kwargs)
def __isub__(self, *args, **kwargs):
f = getattr(self, '__isub__')
return f(*args, **kwargs)
def __imul__(self, *args, **kwargs):
f = getattr(self, '__imul__')
return f(*args, **kwargs)
def __itruediv__(self, *args, **kwargs):
f = getattr(self, '__itruediv__')
return f(*args, **kwargs)
def __ifloordiv__(self, *args, **kwargs):
f = getattr(self, '__ifloordiv__')
return f(*args, **kwargs)
def __imod__(self, *args, **kwargs):
f = getattr(self, '__imod__')
return f(*args, **kwargs)
def __ipow__(self, *args, **kwargs):
f = getattr(self, '__ipow__')
return f(*args, **kwargs)
def __ilshift__(self, *args, **kwargs):
f = getattr(self, '__ilshift__')
return f(*args, **kwargs)
def __irshift__(self, *args, **kwargs):
f = getattr(self, '__irshift__')
return f(*args, **kwargs)
def __iand__(self, *args, **kwargs):
f = getattr(self, '__iand__')
return f(*args, **kwargs)
def __ixor__(self, *args, **kwargs):
f = getattr(self, '__ixor__')
return f(*args, **kwargs)
def __repr__(self, *args, **kwargs):
f = getattr(self, '__repr__')
return f(*args, **kwargs)
def __str__(self, *args, **kwargs):
f = getattr(self, '__str__')
return f(*args, **kwargs)
def __cmp__(self, *args, **kwargs):
f = getattr(self, '__cmp__')
return f(*args, **kwargs)
def __rcmp__(self, *args, **kwargs):
f = getattr(self, '__rcmp__')
return f(*args, **kwargs)
def __nonzero__(self, *args, **kwargs):
f = getattr(self, '__nonzero__')
return f(*args, **kwargs)
def __unicode__(self, *args, **kwargs):
f = getattr(self, '__unicode__')
return f(*args, **kwargs)
但是,我的解决方案至少有两个问题:
class MagicOverrider:
,抛出TypeError: 'function' object is not iterable
答案 0 :(得分:1)
没有钩子或选项可以设置为所有魔术方法打开常规属性处理。你可以做到这一点的唯一方法是重写所有这些,以便单独委托你想要的处理,这有一些重要的限制,其中一个你抓住了:
您的尝试失败,因为您尝试将__slots__
定义为实例方法。 __slots__
根本不应该是任何类型的方法,绝对不是实例方法;它需要是一个序列,并且需要在类定义时处理它以决定类的实例的布局。您还尝试编写__new__
,就像它是一个实例方法一样。即使你没有犯过这些错误,你的大部分方法都会以无限的递归循环结束。
答案 1 :(得分:1)
这很棘手。
因为当通过语言结构触发魔术方法时,Python不会通过正常情况下使用的常规属性检索路径(即使用__getattribute__
等):相反,只要指定了特殊方法对于类,它在类本身的二进制数据结构中标记(由Python解释器中的C代码完成)。这样做是为了使这种用法是快捷的 - 否则,为了获得正确的方法(例如添加或项目检索),代码太多了。而且,也很容易有一些无限的递归循环。
所以 - 魔术方法总是直接用Python检索 - 没有__getattribute__
。
可以做的是让magicmethods本身在它们运行时触发__getattribute__
。如果他们得到的结果与他们不同,他们就会这样说。只需要小心避免无限递归。
至于潜在的魔术方法:因为这需要一个元类,所以只需要在创建强制__getattribute__
的类时让元类包装所需类的所有魔术方法。
下面的代码执行此操作,并包含一个在__getitem__
上放置ad-hoc包装的示例类:
from functools import wraps
from threading import local as thread_local
from types import MethodType
def wrap(name, method):
local_flag = thread_local()
@wraps(method)
def wrapper(*args, **kw):
local_method = method
if not getattr(local_flag, "running", False) and args and not isinstance(args[0], type):
local_flag.running = True
# trigger __getattribute__:
self = args[0]
cls = self.__class__
retrieved = cls.__getattribute__(self, name)
if not retrieved is wrapper:
local_method = retrieved
if isinstance(local_method, MethodType):
args = args[1:]
result = local_method(*args, **kw)
local_flag.running = False
return result
wrapper._wrapped = True
return wrapper
class MetaOverrider(type):
def __init__(cls, name, bases, namespace, **kwd):
super().__init__(name, bases, namespace, **kwd)
for name in dir(cls):
if not (name.startswith("__") and name.endswith("__")):
continue
if name in ("__getattribute__", "__class__", "__init__"):
continue
magic_method = getattr(cls, name)
if not callable(magic_method) or getattr(magic_method, "_wrapped", False):
continue
setattr(cls, name, wrap(name, magic_method))
class TestOverriding(list, metaclass=MetaOverrider):
def __getattribute__(self, attrname):
attr = super().__getattribute__(attrname)
if attrname == "__getitem__":
original = attr
def printergetitem(self, index):
print("Getting ", index)
return original(index)
attr = printergetitem
return attr
它可以使用任何魔术方法 - 当然,如果你只是在创建它之后将魔术方法分配给类本身,它将影响使用的包装方法。但是对于__getattribute__
本身添加的任何魔术包装,它应该有效。