Перехватывает вызовы методов magi c в классе python - PullRequest
3 голосов
/ 10 марта 2020

Я пытаюсь создать класс, который обернет значение, которое будет использоваться в нескольких других объектах. По вычислительным причинам цель состоит в том, чтобы это упакованное значение вычислялось только один раз, а ссылка на значение передавалась его пользователям. Я не верю, что это возможно в vanilla python из-за модели объекта-контейнера. Вместо этого мой подход - это класс-оболочка, который передается вокруг и определяется следующим образом:

class DynamicProperty():

    def __init__(self, value = None):
        # Value of the property
        self.value: Any = value

    def __repr__(self):
        # Use value's repr instead
        return repr(self.value)

    def __getattr__(self, attr):
        # Doesn't exist in wrapper, get it from the value 
        # instead
        return getattr(self.value, attr)

Следующее работает, как и ожидалось:

wrappedString = DynamicProperty("foo")
wrappedString.upper()  # 'FOO'

wrappedFloat = DynamicProperty(1.5)
wrappedFloat.__add__(2)  # 3.5

Однако, неявно вызывая __add__ через обычный синтаксис не выполняется:

wrappedFloat + 2  # TypeError: unsupported operand type(s) for 
                  # +: 'DynamicProperty' and 'float'

Есть ли способ перехватить эти неявные вызовы методов без явного определения методов magi c для DynamicProperty для вызова метода по его атрибуту value?

Ответы [ 2 ]

2 голосов
/ 11 марта 2020

Разговоры о «передаче по ссылке» только смущают вас. Сохраните эту терминологию для языков, где вы можете выбирать и где это имеет значение. В Python вы всегда передаете объекты вокруг - и эта передача эквивалентна «передаче по ссылке» - для всех объектов - от None до int в пул активных асинхронных сетевых подключений пример.

Учитывая это: алгоритм, которым следует язык для извлечения атрибутов из объекта, сложен, имеет детализацию - реализация __getattr__ является лишь верхушкой айсберга. Чтение документа под названием « Модель данных » в полном объеме даст вам лучшее представление о всех механизмах извлечения атрибутов.

Тем не менее, вот как это работает для методы "magi c" или "dunder" - (специальные функции с двумя подчеркиваниями перед и двумя после имени): когда вы используете оператор, который требует существования метода, который его реализует (например, __add__ для + ), язык проверяет класс вашего объекта на наличие метода __add__, а не экземпляра. И __getattr__ в классе может динамически создавать атрибуты только для экземпляров этого класса. Но это не единственная проблема: вы можете создать метакласс (унаследованный от type) и поместить метод __getattr__ в этот метакласс. Для всех запросов, которые вы делаете из Python, будет выглядеть так, как будто ваш объект имеет __add__ (или любой другой метод dunder) в своем классе. Однако для более сложных методов Python не go через обычный механизм поиска атрибутов - он «смотрит» непосредственно на класс, если «более сильный» метод существует «физически». В структуре памяти есть слоты, в которых хранятся классы для каждого из возможных методов обработки ошибок - и они либо ссылаются на соответствующий метод, либо имеют значение «null» (это «доступно для просмотра» при кодировании в C на Python сторона, по умолчанию dir покажет эти методы, если они существуют, или опустите их, если нет). Если их там нет, Python просто «скажет», что объект не реализует эту операцию и точку.

Способ обойти это с таким прокси-объектом, как вы хотите, - создать прокси-класс, который либо содержит методы dunder из класса, который вы хотите обернуть, либо все возможные методы, и после вызова проверьте, реализует ли базовый объект вызываемый метод.

Именно поэтому «серьезный» код будет редко если вообще предложите истинные "прозрачные" прокси-объекты. Существуют исключения, но от «Weakrefs», до «super ()», до concurrent.futures, просто чтобы упомянуть некоторые из них в базовом языке и stdlib, никто не пытается «полностью работающий прозрачный прокси» - вместо этого api больше похоже на то, что вы вызываете метод ".value ()" или ".result ()" для оболочки, чтобы получить сам исходный объект.

Однако, можно сделать, как я описал выше. У меня даже есть небольшой (долго не поддерживаемый) пакет на pypi, который делает это, упаковывая прокси на будущее. Код: https://bitbucket.org/jsbueno/lelo/src/master/lelo/_lelo.py

1 голос
/ 10 марта 2020

Оператор + в вашем случае не работает, потому что DynamicProperty не наследуется от float. См .:

>>> class Foo(float):
    pass

>>> Foo(1.5) + 2
3.5

Итак, вам нужно выполнить какое-то динамическое наследование c:

def get_dynamic_property(instance):

    base = type(instance)

    class DynamicProperty(base):
        pass

    return DynamicProperty(instance)


wrapped_string = get_dynamic_property("foo")
print(wrapped_string.upper())

wrapped_float = get_dynamic_property(1.5)
print(wrapped_float + 2)

Вывод:

FOO
3.5
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...