Определить, был ли вызов __getattribute__ из-за hasattr - PullRequest
0 голосов
/ 23 октября 2018

Я повторно внедряю __getattribute__ для класса.

Я хочу заметить любые неправильные (подразумеваются, конечно, сбои) сбои предоставления атрибутов (поскольку реализация __getattribute__ оказаласьдовольно сложный).Для этого я записываю предупреждение, если мой код не смог найти / предоставить атрибут непосредственно перед поднятием AttributeError.

Я знаю:

  1. __getattribute__ реализациирекомендуется быть настолько малым, насколько это просто.
  2. Реализация __getattribute__ считается неправильной в зависимости от того, как / почему она была вызвана.
  3. Код, обращающийся к атрибуту, может простоа также try/except вместо использования hasattr.

TL; DR : Тем не менее, я хотел бы определить, был ли выполнен вызов __getattribute__ из-заhasattr (стих "подлинная" попытка доступа к атрибуту).

Ответы [ 2 ]

0 голосов
/ 23 октября 2018

Это невозможно, даже через проверку стека.hasattr не создает объект фрейма в стеке вызовов Python, как это написано в C, и пытается проверить последний фрейм Python, чтобы определить, приостановлен ли он в середине вызова hasattr, и подвержен ли он всем ложным негативами ложные срабатывания.

Если вы в любом случае решительно настроены сделать свой лучший выстрел, самый надежный (но все еще хрупкий) кладж, который я могу придумать, - это сделать обезьянку-патч builtins.hasattr с функцией Python создает кадр стека 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 в hasattr фрейме f_locals является тем объектом, которым она должна быть.

Наконец, если ваш __getattribute__ был вызван из созданного декораторомобертка, подкласс __getattribute__ или что-то подобное, которое не будет считаться вызовом из hasattr, даже если обертка или переопределение были вызваны из hasattr, даже если вы хотите, чтобы он считал.

0 голосов
/ 23 октября 2018

Вы можете использовать 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
...