Overridden attribute access does not work (as expected)
-
18-09-2019 - |
Question
The main objective of the following module, is to provide a kind of "constant" semantics for some names.
class ConstantError(Exception):
def __init__(self, msg):
self._msg = msg
class Constant(object):
def __init__(self, name):
self._name = name
def __get__(self, instance, owner):
return instance._content[self._name]
def __set__(self, instance, value):
raise ConstantError, 'Illegal use of constant'
class Constants(object):
def __init__(self, content):
self._content = content
for k in self._content:
setattr(self, k, Constant(k))
num_const = Constants({
'one': 1,
'two': 2
})
When used:
>>> from const import *
>>> dir(num_const)
['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', '_content', 'one', 'two']
So one
and two
are there, but the attribute access is diappointing:
>>> num_const.one
<const.Constant object at 0x7faef4871710>
>>>
Where I wold expect 1
in this case. Where am I wrong?
Solution
The descriptor protocol only works on attributes of the class, not on attributes of instances of a class. See the How-To Guide for Descriptors
OTHER TIPS
You are missing a str() or unicode() method in Constants.
Add:
def __unicode__(self):
return self._name
I think python prevents classes from accessing the descriptor machinery so that they can be manipulated. Otherwise manipulating the descriptor could get very tricky without some kind of 'magical' function, and if you've noticed python tries to keep a lot of the language machinery accessible. To get around this I've often generated the class on the fly. For example, you Constants class could be declared like so:
class Constants(object):
def __new__(cls, content):
class _Constants(object):
pass
constants = _Constants
constants._content = content
for k in constants._content:
setattr(_Constants, k, Constant(k))
return constants
but really, for you purposes you might be better of with:
class Constants(object):
def __init__(self, content):
self._content = content
def __getattr__(self,key):
return self._content[key]