Объявление прокси-свойства дочернему объекту

Мы хотим раскрыть атрибут на фасадном объекте, в то время как он реализуется дочерним объектом. Python позволяет нам сделать это, объявив свойство для чтения/записи.

class Foo:

    def __init__(self):
        self.var1 = "hello"

class Bar:

    def __init__(self):
        self.foo = Foo()

    @property
    def var1(self):
        return self.foo.var1

    @var1.setter
    def var1(self, value):
        self.foo.var1 = value
Вход в полноэкранный режим Выход из полноэкранного режима

В этом коде свойство var1 в Bar является прокси для атрибута var1 экземпляра foo.

При такой реализации каждый раз, когда мы добавляем атрибут в Foo и хотим раскрыть его в Bar, мы должны объявить 6 строк кода. Эта кодовая таблица будет расти и сделает класс более сложным для чтения и сопровождения.

Мы реализуем descriptor proxy_property, чтобы сократить эту кодовую таблицу до одной строки. Вот переделанный класс Bar.

class Bar:
    var1 = property_proxy("foo", "var1")

    def __init__(self):
        self.foo = Foo()
Вход в полноэкранный режим Выход из полноэкранного режима

Дескриптор descriptor в python — это класс, в котором мы реализуем методы dunder __get__, __set__ и __delete__.

Дескрипторы — это мощный протокол общего назначения. Они являются механизмом, стоящим за свойствами, методами, статическими методами, методами класса и super().

Python Doc — Дескриптор HowTo Guide

Вот реализация дескриптора property_proxy.

class property_proxy(object):
    """
    A descriptor based recipe that makes it possible to write shorthands
    that forward attribute access from one object onto another.

    >>> class B:
    >>>     def __init__(self):
    >>>         self.foo = 12
    >>>         self.bar = 12
    >>>
    >>> class A:
    >>>     foo: int = property_proxy("b", "foo")
    >>>     bar: int = property_proxy("b", "bar")
    >>>
    >>>     def __init__(self):
    >>>         self.b = B()
    >>>
    >>> a = A()
    >>> print(a.foo)

    This descriptor avoids writing the code below to establish a proxy
     with a child instance

    >>> class B:
    >>>     def __init__(self):
    >>>         self.foo = 12
    >>>
    >>> class A:
    >>>
    >>>     def __init__(self):
    >>>         self.b = B()
    >>>
    >>>     @property
    >>>     def foo(self):
    >>>         return self.b.foo
    >>>
    >>>     @foo.setter
    >>>     def foo(self, value):
    >>>         self.b.foo = value
    >>>

    """

    def __init__(self, objectName, attrName):
        self.objectName = objectName
        self.attrName = attrName

    def __get__(self, instance, owner=None):
        proxy = getattr(instance, self.objectName)
        if proxy is None:
            raise ValueError(f"{self.objectName} does not exists on {instance}")

        return getattr(proxy, self.attrName)

    def __set__(self, instance, value):
        proxy = getattr(instance, self.objectName)
        if proxy is None:
            raise ValueError(f"{self.objectName} does not exists on {instance}")

        setattr(proxy, self.attrName, value)
Вход в полноэкранный режим Выход из полноэкранного режима

В заключение

В этой заметке мы обнаружили, что раскрытие атрибута дочернего объекта в фасаде потребовало бы от нас большого кодового шаблона и мешало бы чтению и обслуживанию нашего фасада.

Мы реализовали дескриптор property_proxy, чтобы сделать это объявление в одну строку и улучшить согласованность кода. Выигрыш в читабельности значителен.

class Bar:
    var1 = property_proxy("foo", "var1")

    def __init__(self):
        self.foo = Foo()
Вход в полноэкранный режим Выход из полноэкранного режима

Ограничение

Объявление property_proxy имеет 2 недостатка, которые влияют на сопровождаемость кода. Это объявление не позволяет линтеру типа mypy проверить существование и типизацию атрибутов. Рефакторинг имени атрибута в IDE может пропустить это объявление, потому что будет рассматривать атрибуты в объявлении property_proxy как обычный текст.

Подробнее

  • Python Doc — Дескриптор HowTo Guide

Оцените статью
Procodings.ru
Добавить комментарий