implementing descriptors

Dave Angel davea at ieee.org
Fri Aug 14 20:50:10 CEST 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 =e,b = None)
>>>>>       self.start > >>>       self.end >
>>>>> from datetime import datetime
>>>>> c =etime(2009,8,13,6,15,0)
>>>>> d =etime(2009,8,14,12,0,0)
>>>>> afoo =(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 =etime(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 =e,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 =ue
>>>>         else:
>>>>             self._end =ue
>>>>     start =perty(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 =ue
>>>>         else:
>>>>             self._start =ue
>>>>     end =perty(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=oo(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'm going to assume by "writer" you mean yourself?  In other words, the 
interface isn't being forced upon you by someone else?  So I'll continue 
to push for a cleaner/clearer interface.

I'm not going to make any judgment about the relative abilities of you 
and the caller of your class.  You make the final judgment call on how 
much you want to bulletproof your interface.

But occasionally the "automatic fix" causes more problems than it 
solves.  Now, this time you use the word "enforce," which is much 
different than the original implied "fix up."  So I'll assume you mean 
it literally, that you want to detect when the caller has broken the 
rules, and raise an exception in that case.  In either case, you need to 
define what "the rules" are.  And normally this is done by specifying 
invariants.

I think you're declaring an invariant that, at all times, the object has 
two datetime attributes, and that obj.start is no greater than obj.end.  
If the attributes are individually writable, and you do the swap at that 
time, you get into the trouble I described above.  On the other hand, if 
you just do a raise, then your caller would still get into trouble for 
the same sequence of calls.

So the cure is to either make a new method that explicitly changes both, 
or don't do the actual check for order till he tries to read the 
values.   In that latter case, you're changing the invariant subtly, to 
say that if the object is temporarily in an invalid state, one cannot 
call any other methods or fetch values till the state has been 
corrected.  A real pain to describe, but occasionally useful.

This problem of an object temporarily being in an invalid state is not 
an uncommon one.  But most of us have fallen into one of the pitfalls at 
one time or another.

DaveA






More information about the Python-list mailing list