Note: this answer is similar to the OP's answer, but with a few differences worth noting.
After reading the article linked from another relevant SO question, I've come to the following code:
#!/usr/bin/env python
import inspect
class Numberise(object):
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if self.name not in instance.__dict__:
raise (AttributeError, self.name)
return '%o'%(instance.__dict__[self.name])
def __set__(self, instance, value):
print ('setting value to: %d'%value)
instance.__dict__[self.name] = value
class Register(object):
width = Numberise("truewidth")
def __init__(self, width=16, name='unNamed'):
super(Register, self).__init__()
tuple_args = inspect.getargvalues(inspect.currentframe()) #shorthand
for arg in tuple_args[0]:
setattr(self, arg, tuple_args[3][arg])
if __name__ == "__main__":
new_regs = [Register(width=i) for i in range(10)]
for i,reg in enumerate(new_regs):
reg.width = i
for R in new_regs:
print ('In extract(). Id:%s name:%s, width:%s, truewidth:%d'%(id(R), R.name, R.width, R.truewidth))
This program produces the output which I think is what's desired:
setting value to: 0
setting value to: 1
setting value to: 2
setting value to: 3
setting value to: 4
setting value to: 5
setting value to: 6
setting value to: 7
setting value to: 8
setting value to: 9
setting value to: 0
setting value to: 1
setting value to: 2
setting value to: 3
setting value to: 4
setting value to: 5
setting value to: 6
setting value to: 7
setting value to: 8
setting value to: 9
In extract(). Id:35542384 name:unNamed, width:0, truewidth:0
In extract(). Id:35543152 name:unNamed, width:1, truewidth:1
In extract(). Id:35537776 name:unNamed, width:2, truewidth:2
In extract(). Id:36072560 name:unNamed, width:3, truewidth:3
In extract(). Id:36070384 name:unNamed, width:4, truewidth:4
In extract(). Id:36073040 name:unNamed, width:5, truewidth:5
In extract(). Id:36073072 name:unNamed, width:6, truewidth:6
In extract(). Id:36073104 name:unNamed, width:7, truewidth:7
In extract(). Id:36073136 name:unNamed, width:10, truewidth:8
In extract(). Id:36073168 name:unNamed, width:11, truewidth:9
Here's an explanation of what happens. In the line width = Numberise("truewidth")
of Register class, we introduce the descriptor. It is one per class, not one per instance, so no value is stored in Numberise itself: we got to store the actual values in the instances. The descriptor as it is defined allows us to access the member variable self.truewidth
of an instance of Register class. For the purpose of illustration, the __get__
method returns not the truewidth
(which would be return instance.__dict__[self.name]
), but its string representation as an octal number. Printing R.width
is accessing it via descriptor. Printing R.truewidth
is accessing it directly.
We could have called the member variable width
, the same as the descriptor, and there would be no naming conflict: the descriptor is a part of the class namespace, and the member variable is a part of each instance's namespace. So, truewidth
is used only for clarity, to better distinguish the two entities. In real code, perhaps naming it width
is better, so that the actual data is hidden behind the descriptor, and you can't access it by accident.
Also, the program was made both Python2 and Python3 friendly, just by adding parentheses to the lines with raise
and print
.