В Mental Game на YouTube Рэймонда Хеттингера:

class Validator:

    def __set_name__(self, owner, name):
        self.private_name = f'_{name}'    

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.private_name)

    def __set__(self, obj, value):
        self.validate(value)
        setattr(obj, self.private_name, value)

Доу-Ран Лиу утверждает в Написание дескрипторов в Python 3.6+:

[...] вместо использования встроенных функций getattr и setattr нам нужно напрямую обратиться к объекту dict , потому что встроенные функции также будут перехвачены протоколами дескрипторов и вызовут ошибку RecursionError.

class Validator:

    def __set_name__(self, owner, name):
        self.name= name
    
    def __get__(self, obj, objtype=None):
        return obj.__dict__[self.name]

    def __set__(self, obj, value):
        self.validate(value)
        obj.__dict__[self.name] = value

Но в описании дескрипторов на YouTube говорится:

from weakref import WeakKeyDictionary

class Validator:
    def __init__(self):
        self.data = WeakKeyDictionary()

    def __get__(self, obj, owner):
        return self.data[obj]

    def __set__(self, obj, value):
        self.validate(value)
        self.data[obj] = value

Каким будет правильный способ реализации дескрипторов?

3
Agent49 25 Июл 2020 в 14:03

1 ответ

Лучший ответ

Первый пример хорош. Все три являются допустимыми реализациями.

Не уверен, почему второй автор говорит, что вы не можете использовать getattr таким образом. Да, getattr вызывает протокол дескриптора, но дескриптор назначается для type(obj).__dict__[name], но вы устанавливаете private_name как f'_{name}', чтобы не было бесконечной рекурсии ... Было бы , если бы вы использовали self.private_name = name в __set_name__ вместо self.private_name = f'_{name}', но это не то, что делают первые два ...

РЕДАКТИРОВАТЬ: читая эту ссылку, это то, что делает автор ...

При этом второе решение не неверно .

Что касается третьего решения, я предполагаю, что это альтернатива, которая вообще не загрязняет пространство имен экземпляров, сохраняя отдельное пространство имен - WeakKeyDictionary. Неплохая идея, но она не более «правильная», чем два других. Обратите внимание, что он действительно предполагает, что хэши класса основаны на идентичности, что не так. Вы можете реализовать __hash__ в классе для хеширования на основе чего-то еще, что нормально, если ваш класс "концептуально" неизменен, например некоторый класс Point(x, y), который не предоставляет никаких методов мутатора, и хеширует его на основе значений x и y. Таким образом, это сделало бы этот подход немного более ограничительным, но в остальном это умное решение, чтобы не возиться с пространством имен экземпляров.

Я полагаю, что первый из них является наиболее питоническим в том смысле, что он идиоматичен. Но опять же, все три решения являются действительными.

1
juanpa.arrivillaga 25 Июл 2020 в 11:29