从帧中检测方法与 class 相关联

Detect, from a frame, which class a method is associated with

我正在 Python 中编写一段非常骇人听闻的非生产代码,我需要一些方法来检测 _XYZ__foo 属性访问是否从定义于一个叫 /_*XYZ/ 的 class。不过,这并不那么容易,因为我需要检测 原始 方法访问,以防任何内容被覆盖 __getattribute__ 并调用 super().

我不擅长解释,所以...规则类似于Java的private,除了我想防止作弊。 (是的,我知道这与 Python 理念背道而驰;请耐心等待。)

我目前的攻击计划是:

  1. 使用 re.compile('_(?P<class>.*?)__(?P<name>.*)') 检测 class 的名称(删除前面的 _)。
  2. 使用 sys._getframe(n) 攀登 super 链,找出属性访问的位置。
  3. 检测class它在...不知何故。我被困在这里了。

我可以通过模拟 super 的 MRO 运行来做到这一点,但我更愿意依赖检测,因为检查 super 调用了什么以及调用了什么通过用户功能很难。

所以,对于我的实际问题。给定一个框架,我如何检测方法与哪个 class 相关联?如果我可以访问函数对象,我可以 f.__qualname__[:-1-len(f.__name__)],但我没有(或者,至少,我 认为 我没有)。事实上,我不知道该怎么做!

这是一个演示我想要做什么的简单示例:

import sys
import re
import itertools
import builtins
from builtins import __build_class__

def build_class(func, name, *bases, metaclass=None, **kwds):
    if bases[-1] is object:
        bases = bases[:-1]
    bases += HackishClass, object
    if metaclass is None:
        return __build_class__(func, name, *bases, **kwds)
    return __build_class__(func, name, *bases, metaclass=metaclass, **kwds)

private_regex = re.compile('_(?P<class>.*?)__(?P<name>.*)')
class HackishClass:
    __slots__ = ()
    def __getattribute__(self, key):
        match = private_regex.match(key)
        if match is not None:
            for depth in itertools.count(1):
                frame = sys._getframe(depth)
                if ...:  # snip
                    # Check for the original attribute access here.
                    break
            class_name = ...  # HERE! MAGIC GOES HERE!
            if class_name != match['class']:
                raise AttributeError("This is private! Keep out.")
        return super().__getattribute__(key)

builtins.__build_class__ = build_class

据我所知,没有办法直接从框架对象中获取属性访问发生的方法。但是,我们可以获得该方法的 code object。然后我们可以搜索对象的 MRO,直到找到该代码对象所属的方法。

private_regex = re.compile('_(?P<class>.*?)__(?P<name>.*)')
class HackishClass:
    __slots__ = ()

    def __getattribute__(self, key):
        match = private_regex.match(key)
        if match is None:
            # not a private attribute, no problem
            return super().__getattribute__(key)

        # obtain the code object of the calling function
        calling_codeobj = inspect.currentframe().f_back.f_code

        # iterate the MRO until we find a class with the name from `key`
        classname = match.group('class')
        for cls in type(self).mro():
            if cls.__name__ != classname:
                continue

            # check if the code object belongs to a method defined in this class
            for thing in vars(cls).values():
                if getattr(thing, '__code__', None) is calling_codeobj:
                    # found it! allow the attribute access
                    return super().__getattribute__(key)

        raise AttributeError("This is private! Keep out.")

一个小演示:

class Foo:
    def __init__(self):
        self.__foo = 5
        print(self.__foo)

f = Foo()           # prints 5
print(f._Foo__foo)  # throws AttributeError