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__.
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to