即使通过堆栈检查,这是不可能的。hasattr
在 Python 调用堆栈中不生成任何帧对象,因为它是用 C 编写的,并尝试检查最后一个 Python 帧以猜测它是否挂在一个中间hasattr
调用很容易出现各种误报和误报。
如果你绝对决心无论如何都要尽力而为,我能想到的最可靠(但仍然脆弱)的拼凑就是猴子补丁builtins.hasattr
与一个Python函数does生成一个 Python 堆栈框架:
import builtins
import inspect
import types
_builtin_hasattr = builtins.hasattr
if not isinstance(_builtin_hasattr, types.BuiltinFunctionType):
raise Exception('hasattr already patched by someone else!')
def hasattr(obj, name):
return _builtin_hasattr(obj, name)
builtins.hasattr = hasattr
def probably_called_from_hasattr():
# Caller's caller's frame.
frame = inspect.currentframe().f_back.f_back
return frame.f_code is hasattr.__code__
Calling probably_called_from_hasattr
inside __getattribute__
然后将测试您的__getattribute__
可能是从hasattr
。这避免了假设调用代码使用名称“hasattr”,或者名称“hasattr”的使用对应于这个特定的情况。__getattribute__
打电话,或者说hasattr
调用源自 Python 级代码而不是 C。
这里脆弱性的主要来源是如果有人保存了对真实的引用hasattr
在猴子补丁通过之前,或者如果其他猴子补丁通过hasattr
(例如,如果有人将此代码复制粘贴到同一程序中的另一个文件中)。这isinstance
检查尝试捕获其他人猴子修补的大多数情况hasattr
摆在我们面前,但它并不完美。
另外,如果hasattr
在用 C 编写的对象上触发对您的对象的属性访问,这看起来像您的__getattribute__
被叫自hasattr
。这是最有可能出现误报的方法;上一段中的所有内容都会给出假阴性。您可以通过检查条目来防止这种情况obj
in the hasattr
框架的f_locals
是它应该是的对象。
最后,如果你的__getattribute__
是从装饰器创建的包装器、子类中调用的__getattribute__
或类似的内容,这不会被视为来自hasattr
,即使包装器或重写是从hasattr
,即使您希望它计数。