I just wanted to use the descriptor pattern, but it didn't seem to work that well. Here is a short example (without any real use, just to show):
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
and the Test:
>>>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
What is my misconception here?
Descriptors only work when they are attributes of a class, not an instance. If you change your class to:
class Test(object):
num = Num()
. . . then the descriptor will work.
However, because the descriptor has to be set on the class, that means there is only one instance of the descriptor, so it's probably not a good idea for the descriptor to store its values on self
. Such values will be shared across all instances of the class. Instead, set the value on instance
.
Also, note that your __str__
and __repr__
will probably not do what you think they will. Calling t.num
will activate the descriptor and return its val
, so the result of t.num
will be the plain number 0, not a Num
instance. The whole point of the descriptor is to transparently return the result of __get__
without making the descriptor object itself visible.
Here are some illustrative examples:
>>> 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
With an alternate version of the descriptor:
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