Получение питонов __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)
Свойства помогают контролировать, как устанавливаются переменные экземпляра.