implementing descriptors

Ethan Furman ethan at stoneleaf.us
Fri Aug 14 21:30:39 EDT 2009


dippim wrote:
> On Aug 14, 10:48 am, Dave Angel <da... at ieee.org> wrote:
> 
>>dippim wrote:
>>
>>>On Aug 14, 2:34 am, Raymond Hettinger <pyt... at rcn.com> wrote:
>>
>>>>[David]
>>
>>>>>I am new to Python and I have a question about descriptors.  If I have
>>>>>a class as written below, is there a way to use descriptors to be
>>>>>certain that the datetime in start is always before the one in end?
>>
>>>>>class foo(object):
>>>>>   def __init__(self,a =one,b = None)
>>>>>      self.start =
>>>>>      self.end =
>>
>>>>>from datetime import datetime
>>>>>c =atetime(2009,8,13,6,15,0)
>>>>>d =atetime(2009,8,14,12,0,0)
>>>>>afoo =oo(c,d)
>>
>>>>>For instance, if the following code were run, I would like to instance
>>>>>of foo to switch the start and end times.
>>
>>>>>afoo.start =atetime(2010,8,13,6,15,0)
>>
>>>>>I was thinking of using the __set__ descriptor to catch the assignment
>>>>>and reverse the values if necessary, but I can't figure out how to
>>>>>determine which values is being set.
>>
>>>>You're on the right track, but it is easier to use property() than to
>>>>write your own custom descriptor with __get__ and __set__.
>>
>>>>class foo(object):
>>>>    def __init__(self,a =one,b = None):
>>>>        self._start =
>>>>        self._end =
>>>>    def get_start(self):
>>>>        return self._start
>>>>    def set_start(self, value):
>>>>        if self._end is None or value < self._end:
>>>>            self._start =alue
>>>>        else:
>>>>            self._end =alue
>>>>    start =roperty(get_start, set_start)
>>>>    def get_end(self):
>>>>        return self._end
>>>>    def set_end(self, value):
>>>>        if self._start is None or value > self._start:
>>>>            self._end =alue
>>>>        else:
>>>>            self._start =alue
>>>>    end =roperty(get_end, set_end)
>>
>>>>Raymond
>>
>>>Raymond,
>>>   This functionality is exactly what I was looking for. Thanks!  I'll
>>>be using this to solve my problem.
>>
>>>   Now that I'm on the right track, I'm still a bit confused about how
>>>__get__ and __set__ are useful.  Admittedly, I don't need to
>>>understand them to solve this problem, but perhaps they may be useful
>>>in the future.  If I wanted to solve this problem using __get__ and
>>>__set__ could it be done?
>>
>>>Thanks Again!
>>
>>DANGER- WILL ROBINSON!
>>
>>Don't use this code as-is.  There is a nasty surprise waiting for the
>>caller when he sets start and end, and discovers that one of them gets
>>thrown out, and an old value still remains.
>>
>>obj= foo(3, 5)
>>obj.start = 8
>>obj.end = 12
>>print obj.start, obj.end
>>
>>will print out  3, 12.    Not what the caller expected.
> 
> 
> You're right about this and I appreciate the warning, but I think what
> Raymond was going for was directional accuracy without a great deal of
> his time wasted on details.  The explanation served the purpose of
> moving me forward using property() and I'm thankful for it.
> 
> 
>>Four fixes, in order of preference:
>>0) Trust your user to read and obey your docstrings.  This was what JM
>>was implying, by changing the names of the formal parameters.
> 
> 
> Considering I am not the sharpest knife in the drawer, I don't wish to
> start a philosophical discussion about the relative aptitude and
> capabilities of the people who might use a class I build.  However, I
> will say that as this particular requirement is imposed on this class
> by the writer, shouldn't it be the writer's responsibility to enforce
> it, especially, when the cost of enforcement is so low?
> 
> 
>>1)  make a new method that sets both values, making these two properties
>>readonly.  That new method would make sure the two parameters are
>>self-consistent.  Making the actual values readonly can be done with a
>>descriptor as well, or even a decorator.
> 
> 
>>2) Raise an exception in the getter methods if they're out of order
>>3) do the min/max logic on the getter methods, but I don't like that one
>>at all.
>>
>>DaveA
> 

I am in complete agreement with DaveA on this issue -- if you want the 
invariant, then have a routine that set's both at once.

The head-aches it would cause me, at least, to have to gyrate around 
whether I was making the object later or earlier in time (having to swap 
the order I assign stand and end, assuming I even know whether I'm going 
forward or backward without further checking... argh!) would quite 
possibly cause me to choose some other routine instead.

How about something like (untested):
     def set(self, start=None, end=None):
         if start is None:
             start = self.start
         if end is None:
             end = self.end
         if start > end:
             raise ValueError('blah blah')
         self.start = start
         self.end = end

This way you could still set only one, but have bounds checking in place.

~Ethan~



More information about the Python-list mailing list