检测 __getattribute__ 调用是否由 hasattr 引起
Detect if a __getattribute__ call was due to hasattr
我正在为 class.
重新实现 __getattribute__
我想注意提供属性的任何不正确(当然意味着失败是预期的)失败(因为 __getattribute__
实现非常复杂)。为此,如果我的代码在引发 AttributeError
.
之前无法 find/provide 属性,我会记录一条警告
我知道:
__getattribute__
鼓励实施尽可能简单。
__getattribute__
实现根据调用的 how/why 表现不同被认为是错误的。
- 访问属性的代码也可以
try/except
而不是使用 hasattr
。
TL;DR:尽管如此,我想检测是否由于 hasattr
而调用 __getattribute__
(节 "genuine" 尝试访问该属性)。
您可以使用 sys._getframe
获取调用者框架并使用 inspect.getframeinfo
获取进行调用的代码行,然后使用某种解析机制,例如正则表达式(您可以不要使用 ast.parse
因为一行代码通常是一个不完整的语句)来查看 hasattr
是否是调用者。它不是很健壮,但它应该在大多数合理的情况下工作:
import inspect
import sys
import re
class A:
def __getattribute__(self, item):
if re.search(r'\bhasattr\b', inspect.getframeinfo(sys._getframe(1)).code_context[0]):
print('called by hasattr')
else:
print('called by something else')
hasattr(A(), 'foo')
getattr(A(), 'foo')
这输出:
called by hasattr
called by something else
这是不可能的,即使通过堆栈检查也是如此。 hasattr
在 Python 调用堆栈中不生成任何帧对象,因为它是用 C 编写的,并试图检查最后一个 Python 帧以猜测它是否在 [= 中间挂起11=] 调用容易出现各种误报和误报。
如果你下定决心无论如何都要尽力而为,我能想到的最可靠(但仍然脆弱)的拼凑就是使用 Python 猴子补丁 builtins.hasattr
产生 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__
在 __getattribute__
中调用 probably_called_from_hasattr
将测试您的 __getattribute__
是否可能是从 hasattr
调用的。这避免了假设调用代码使用名称 "hasattr",或者名称 "hasattr" 的使用对应于这个特定的 __getattribute__
调用,或者 hasattr
调用起源于 Python 级代码而不是 C.
这里脆弱性的主要来源是,如果有人在猴子补丁通过之前保存了对真实 hasattr
的引用,或者如果其他人猴子补丁 hasattr
(例如有人将此代码复制粘贴到同一程序中的另一个文件中)。 isinstance
检查试图在我们之前捕获大多数其他人进行猴子修补 hasattr
的情况,但它并不完美。
此外,如果用 C 编写的对象上的 hasattr
触发了对您的对象的属性访问,那看起来您的 __getattribute__
是从 hasattr
调用的。这是最有可能得到误报的方法;上一段中的所有内容都会给出假阴性。您可以通过检查 hasattr
框架的 f_locals
中 obj
的条目是否是它应该是的对象来防止这种情况发生。
最后,如果你的 __getattribute__
是从装饰器创建的包装器、子类 __getattribute__
或类似的东西调用的,那将不会算作来自 hasattr
的调用,即使包装器或覆盖是从 hasattr
调用的,即使您希望它计数。
我正在为 class.
重新实现__getattribute__
我想注意提供属性的任何不正确(当然意味着失败是预期的)失败(因为 __getattribute__
实现非常复杂)。为此,如果我的代码在引发 AttributeError
.
我知道:
__getattribute__
鼓励实施尽可能简单。__getattribute__
实现根据调用的 how/why 表现不同被认为是错误的。- 访问属性的代码也可以
try/except
而不是使用hasattr
。
TL;DR:尽管如此,我想检测是否由于 hasattr
而调用 __getattribute__
(节 "genuine" 尝试访问该属性)。
您可以使用 sys._getframe
获取调用者框架并使用 inspect.getframeinfo
获取进行调用的代码行,然后使用某种解析机制,例如正则表达式(您可以不要使用 ast.parse
因为一行代码通常是一个不完整的语句)来查看 hasattr
是否是调用者。它不是很健壮,但它应该在大多数合理的情况下工作:
import inspect
import sys
import re
class A:
def __getattribute__(self, item):
if re.search(r'\bhasattr\b', inspect.getframeinfo(sys._getframe(1)).code_context[0]):
print('called by hasattr')
else:
print('called by something else')
hasattr(A(), 'foo')
getattr(A(), 'foo')
这输出:
called by hasattr
called by something else
这是不可能的,即使通过堆栈检查也是如此。 hasattr
在 Python 调用堆栈中不生成任何帧对象,因为它是用 C 编写的,并试图检查最后一个 Python 帧以猜测它是否在 [= 中间挂起11=] 调用容易出现各种误报和误报。
如果你下定决心无论如何都要尽力而为,我能想到的最可靠(但仍然脆弱)的拼凑就是使用 Python 猴子补丁 builtins.hasattr
产生 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__
在 __getattribute__
中调用 probably_called_from_hasattr
将测试您的 __getattribute__
是否可能是从 hasattr
调用的。这避免了假设调用代码使用名称 "hasattr",或者名称 "hasattr" 的使用对应于这个特定的 __getattribute__
调用,或者 hasattr
调用起源于 Python 级代码而不是 C.
这里脆弱性的主要来源是,如果有人在猴子补丁通过之前保存了对真实 hasattr
的引用,或者如果其他人猴子补丁 hasattr
(例如有人将此代码复制粘贴到同一程序中的另一个文件中)。 isinstance
检查试图在我们之前捕获大多数其他人进行猴子修补 hasattr
的情况,但它并不完美。
此外,如果用 C 编写的对象上的 hasattr
触发了对您的对象的属性访问,那看起来您的 __getattribute__
是从 hasattr
调用的。这是最有可能得到误报的方法;上一段中的所有内容都会给出假阴性。您可以通过检查 hasattr
框架的 f_locals
中 obj
的条目是否是它应该是的对象来防止这种情况发生。
最后,如果你的 __getattribute__
是从装饰器创建的包装器、子类 __getattribute__
或类似的东西调用的,那将不会算作来自 hasattr
的调用,即使包装器或覆盖是从 hasattr
调用的,即使您希望它计数。