reuse validation logic with descriptors

Steven Bethard steven.bethard at gmail.com
Tue Mar 1 15:42:45 EST 2005


David S. wrote:
> Steven Bethard <steven.bethard <at> gmail.com> writes:
>>
>>Looks like you're trying to reinvent the property descriptor.  Try using 
>>the builtin property instead:
>>
>>py> def getchar(self):
>>...     if not hasattr(self, '_char'):
>>...         self._char = None
>>...     return self._char
>>...
>>py> def setchar(self, value):
>>...     if not len(value) == 1:
>>...         raise ValueError
>>...     self._char = value
>>...
>>py> singlechar = property(getchar, setchar)
>>py> class Flags(object):
>>...     a = singlechar
>>...     b = singlechar
>>...
> 
> This still fails to work for instances variables of the class.  That is 
> if I use your property in the following:
> py> ...class Flags(object):
> ...        def __init__(self): 
> ...             a = singlechar
> ...
> py> f = Flags()
> py> f.a = "a"


Yes, you need to assign it at the class level, as you will for any 
descriptor.  Descriptors only function as attributes of type objects. 
But note that as I've used them above, they do work on a per-instance 
basis.  What is it you're trying to do by assigning them in __init__? 
Do you want different instances of Flags to have different descriptors?

> Also, it seems that using a property, I can not do the other useful 
> things I can do with a proper class, like provide an __init__, __str__, 
> or __repr__.  

Well, you can write your own descriptors that do things much like 
property, but note that __init__ will only be invoked when you first 
create them, and __str__ and __repr__ will only be invoked when you 
actually return the descriptor object itself.  For example:

py> class Descr(object):
...     def __init__(self):
...         self.value = None
...     def __repr__(self):
...         return 'Descr(value=%r)' % self.value
...
py> class DescrSelf(Descr):
...     def __get__(self, obj, type=None):
...         return self
...
py> class DescrObj(Descr):
...     def __get__(self, obj, type=None):
...         return obj
...
py> class DescrValue(Descr):
...     def __get__(self, obj, type=None):
...         return obj.value
...
py> class C(object):
...     s = DescrSelf()
...     o = DescrObj()
...     v = DescrValue()
...
py> C.s
Descr(value=None)
py> print C.o
None
py> C.v
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
   File "<interactive input>", line 3, in __get__
AttributeError: 'NoneType' object has no attribute 'value'
py> c = C()
py> c.s
Descr(value=None)
py> c.o
<__main__.C object at 0x011B65F0>
py> c.v
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
   File "<interactive input>", line 3, in __get__
AttributeError: 'C' object has no attribute 'value'
py> c.value = False
py> c.s
Descr(value=None)
py> c.o
<__main__.C object at 0x011B65F0>
py> c.v
False

The point here is that, if you define __repr__ for a descriptor, it will 
only be invoked when the descriptor itself is returned.  But you're 
storing your string as an attribute of the descriptor (like 'value' 
above), so you want the __repr__ on this attribute, not on the 
descriptor itself.  As you'll note from the code above, the only time 
__repr__ is called is when the descriptor returns 'self'.  But for 
almost all purposes, you're going to want to do something like 
DescrValue does (where you return an attribute of the object, not of the 
descriptor).

If you want a single-character string type as an attribute, why not 
subclass str and use a property?

py> class SingleChar(str):
...     def __new__(cls, s):
...         if len(s) != 1:
...            raise ValueError
...         return super(SingleChar, cls).__new__(cls, s)
...     def __repr__(self):
...         return 'SingleChar(%s)' % super(SingleChar, self).__repr__()
...
py> def getchar(self):
...     return self._char
...
py> def setchar(self, value):
...     self._char = SingleChar(value)
...
py> singlechar = property(getchar, setchar)
py> class Flags(object):
...     a = singlechar
...     b = singlechar
...
py> f = Flags()
py> f.a = "a"
py> f.a
SingleChar('a')
py> f.b = "bb"
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
   File "<interactive input>", line 2, in setchar
   File "<interactive input>", line 4, in __new__
ValueError

Note that now (unlike if you'd used only a descriptor), the __repr__ is 
correctly invoked.

STeVe

P.S.  If you haven't already, you should read 
http://users.rcn.com/python/download/Descriptor.htm a couple of times. 
It took me until about the third time I read it to really understand 
what descriptors were doing.  The big thing to remember is that for an 
instance b,
     b.x
is equivalent to
     type(b).__dict__['x'].__get__(b, type(b))
and for a class B,
     B.x
is equivalent to
     B.__dict__['x'].__get__(None, B)
Note that 'x' is always retrieved from the *type* __dict__, not from the 
*instance* __dict__.



More information about the Python-list mailing list