Получение питонов __set__ работает

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

class Num(object):
  def__init__(self, val=0):
    self.val = val
  def __get__(self, instance, owner):
    return self.val
  def __set__(self, instance, val):
    self.val = val
  def __str__(self):
    return "Num(%s)" % self.val
  def __repr__(self):
    return self.__str__()

class Test(object):
  def __init__(self, num=Num()):
    self.num = num

и тест:

>>>t = Test()
>>>t.num # OK
Num(0)
>>>t.num + 3 #OK i know how to fix that, but I thought __get__.(t.num, t, Test) will be called
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'Num' and 'int'
>>> t.num = 4 # why isn't __set__(t.num, t, 4) called here?
>>> t.num
4

В чем мое заблуждение здесь?

2 ответа

Решение

Дескрипторы работают только тогда, когда они являются атрибутами класса, а не экземпляра. Если вы измените свой класс на:

class Test(object):
    num = Num()

,,, тогда дескриптор будет работать.

Тем не менее, поскольку дескриптор должен быть установлен в классе, это означает, что существует только один экземпляр дескриптора, поэтому, вероятно, для дескриптора не стоит хранить его значения в self, Такие значения будут общими для всех экземпляров класса. Вместо этого установите значение на instance,

Кроме того, обратите внимание, что ваш __str__ а также __repr__ вероятно, не будет делать то, что вы думаете, они будут. призвание t.num активирует дескриптор и вернет его valтак что результат t.num будет обычное число 0, а не Num пример. Весь смысл дескриптора в том, чтобы прозрачно вернуть результат __get__ не делая объект дескриптора видимым.

Вот несколько иллюстративных примеров:

>>> t1 = Test()
>>> t2 = Test()
>>> t1.num
0
>>> Test.num
0
# Accessing the descriptor object itself
>>> Test.__dict__['num']
Num(0)
>>> t1.num = 10
>>> t1.num
10
# setting the value changed it everywhere
>>> t2.num
10
>>> Test.num
10

С альтернативной версией дескриптора:

class Num(object):
  def __init__(self, val=0):
    self.val = val

  def __get__(self, instance, owner):
    try:
        return instance._hidden_val
    except AttributeError:
        # use self.val as default
        return self.val

  def __set__(self, instance, val):
    instance._hidden_val = val

class Test(object):
    num = Num()

>>> t1 = Test()
>>> t2 = Test()
>>> t1.num
0
>>> t1.num = 10
>>> t1.num
10
# Now there is a separate value per instance
>>> t2.num
0

Как заявил BrenBarn, дескрипторы, по-видимому, предназначены для переменных класса. Возможно, вам будет интересно посмотреть на свойства Pythons.

class GenericItem(object):
    """Generic item descriptor"""

    def __init__(self, value=None, name=""):
        super().__init__()

        self.value = value
        self.name = name
    # end Constructor

    def __get__(self, obj, objtype):
#         print(self, obj, objtype)
        return self.value
    # end __get__

    def __set__(self, obj, value):
#         print(self, obj, value)
        self.value = value
    # end __set__

    def __str__(self):
        if self.name is None or self.name == "":
            return str(self.value)
        return self.name +"("+str(self.value)+")"
    # end __str__
# end class Num

class Test(object):
    def __init__(self, value=0):
        super().__init__()

        self._num = GenericItem(value, "Number")
    # end Constructor

    @property
    def num(self):
        """This is a number"""
        return self._num
    @num.setter
    def num(self, value):
        self._num.__set__(None, value)
    # end num property
# end class Test

if __name__ == "__main__":
    g = GenericItem(1, "Generic")
    print(g)
    g = 5
    print(g)


    t = Test()
    print(t.num)
    try:
        t.num + 3 # We didn't implement any sort of addition __add__
    except:
        pass
    t.num = 4
    print(t.num)

Результат:

Generic(1)
5
Number(0)
Number(4)

Свойства помогают контролировать, как устанавливаются переменные экземпляра.

Другие вопросы по тегам