检测__getattribute__调用是否归因于hasattr

时间:2018-10-23 02:41:59

标签: python getattribute hasattr

我正在为课程重新实现__getattribute__

我想注意到提供属性的任何不正确(当然,这是预期的失败)(因为__getattribute__实现非常复杂)。为此,如果我的代码在引发AttributeError之前无法找到/提供属性,则会记录一条警告。

我知道:

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

TL; DR :尽管如此,我还是想检测是否由于__getattribute__而对hasattr进行了呼叫(相反,尝试访问“ {属性)。

2 个答案:

答案 0 :(得分:2)

这是不可能的,即使通过堆栈检查也是如此。 hasattr在Python调用堆栈中不生成任何框架对象,因为它是用C语言编写的,因此尝试检查最后一个Python框架以猜测它是否在hasattr调用中间被暂停,这很容易发生各种误报和误报。

如果您绝对决心尽一切所能,那么我能想到的最可靠(但仍很脆弱)的挥霍动作是使用做的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的情况,但这并不完美。

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

最后,如果您的__getattribute__是由装饰器创建的包装器,子类__getattribute__或类似的东西调用的,则即使包装器被调用为hasattr也不会算作调用或覆盖是从hasattr调用的,即使您希望它计数也是如此。

答案 1 :(得分:-1)

您可以使用sys._getframe获取调用者框架,并使用inspect.getframeinfo获取进行调用的代码行,然后使用诸如regex之类的解析机制(您不能请使用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