Определить из фрейма, с каким классом связан метод - PullRequest
0 голосов
/ 28 августа 2018

Я пишу невероятно хакерский кусок не совсем производственного кода на Python, и мне нужен какой-то способ определить, был ли вызван доступ к атрибуту _XYZ__foo из метода, определенного в классе с именем /_*XYZ/. Однако это не так просто, так как мне нужно обнаружить доступ к методу original в случае, если что-то переопределило __getattribute__ и вызвало super().

Я плохо объясняю, поэтому ... правила похожи на private в Java, за исключением того, что я хочу предотвратить обман. (Да, я знаю, что это противоречит философии Python; потерпите меня здесь.)

Мой текущий план атаки:

  1. Используйте re.compile('_(?P<class>.*?)__(?P<name>.*)'), чтобы определить имя класса (с предыдущими _ s удалены).
  2. Поднимитесь по цепочке super с помощью sys._getframe(n), чтобы узнать, где был доступ к атрибуту.
  3. Определить, в каком классе он был ... каким-то образом. Я застрял здесь.

Я мог бы сделать это, эмулируя ходьбу MRO super, но я бы предпочел опираться на обнаружение, потому что проверить, что было вызвано super и что было вызвано пользовательскими функциями, сложно.

Итак, к моему актуальному вопросу. По заданному кадру, как я могу определить, с каким классом связан метод? Если бы у меня был доступ к объекту функции, я мог бы сделать 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

1 Ответ

0 голосов
/ 29 августа 2018

Насколько я знаю, нет способа получить метод, при котором доступ к атрибуту происходил непосредственно из объекта кадра. Однако мы можем получить объект кода этого метода . Затем мы можем искать 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
...