1

我正在为一个班级重新实施__getattribute__

我想注意提供属性的任何不正确(当然意味着失败)失败(因为__getattribute__实现结果非常复杂)。为此,如果我的代码在引发AttributeError.

我意识到:

  1. __getattribute__鼓励实现尽可能简单。
  2. __getattribute__根据调用的方式/原因,实现的行为不同被认为是错误的。
  3. 访问属性的代码也可以try/except代替使用hasattr.

TL; DR:不过,我想检测是否__getattribute__由于hasattr(与访问属性的“真正”尝试相比)完成了调用。

4

2 回答 2

3

这是不可能的,即使通过堆栈检查也是如此。hasattr在 Python 调用堆栈中不会产生帧对象,因为它是用 C 编写的,并且试图检查最后一个 Python 帧以猜测它是否在hasattr调用中间暂停很容易出现各种漏报和误报。

如果您绝对决心无论如何都要尽力而为,那么我能想到的最可靠(但仍然很脆弱)的kludge是使用确实产生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__

然后调用probably_called_from_hasattr内部__getattribute__将测试您__getattribute__是否可能是从 调用的hasattr。这避免了假设调用代码使用了名称“hasattr”,或者名称“hasattr”的使用对应于这个特定__getattribute__的调用,或者hasattr调用源自 Python 级代码而不是 C。

这里脆弱性的主要来源是如果有人hasattr在猴子补丁通过之前保存了对真实的引用,或者如果其他人在猴子补丁hasattr中(例如,如果有人将此代码复制粘贴到同一程序中的另一个文件中)。该isinstance检查试图捕捉大多数其他人hasattr在我们面前修补猴子的情况,但它并不完美。

此外,如果hasattr在用 C 编写的对象上触发对您的对象的属性访问,那看起来就像__getattribute__是从hasattr. 这是最有可能得到误报的方法;上一段中的所有内容都会给出假阴性。您可以通过检查框架obj中的条目是否是它应该是的对象来防止这种情况。hasattrf_locals

最后,如果你__getattribute__是从装饰器创建的包装器、子类或类似的东西中调用的,那么即使包装器或覆盖是从调用的,即使你希望它计数__getattribute__,也不会算作调用。hasattrhasattr

于 2018-10-23T03:54:00.040 回答
-2

您可以使用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
于 2018-10-23T03:16:01.643 回答