Descriptors and side effects

Bruno Desthuilliers bruno.42.desthuilliers at wtf.websiteburo.oops.com
Mon Nov 5 04:07:38 EST 2007


mrkafk at gmail.com a écrit :
> Hello everyone,
> 
> I'm trying to do seemingly trivial thing with descriptors: have
> another attribute updated on dot access in object defined using
> descriptors.
> 
> For example, let's take a simple example where you set an attribute s
> to a string and have another attribute l set automatically to its
> length.
> 
>>>> class Desc(str):
> 	def __init__(self,val):
> 		self.s=val
> 		self.l=len(val)
> 		print "creating value: ", self.s
> 		print "id(self.l)", id(self.l)
> 	def __set__(self, obj, val):
> 		self.s=val
> 		self.l=len(val)
> 		print "setting value:", self.s, "length:", self.l
> 	def __get__(self, obj, type=None):
> 		print "getting value:", self.s, "length:", self.l
> 		return self.l
> 
> 
>>>> class some(str):
> 	m=Desc('abc')
> 	l=m.l

First point : I don't get why Desc and some derive from str. Second 
point: I don't get why you're storing the value and it's length in the 
descriptor itself - obviously, you can't expect one descriptor instance 
to be mapped to 2 distinct attributes. Third point: you understand that, 
the way you wrote it, your descriptor will behave as a class (ie:shared) 
attribute, don't you. Fourth point: if you hope some.l to be rebound 
when m.l is, then you should learn Python basics before trying to jump 
into descriptors.

The obvious, simple way to some your problem is to use a couple of 
properties:

class Some(object):
   @apply
   def m():
     def fget(self):
       return self._m
     def fset(self, val):
       self._m = val
       self._l = len(val)
     return property(**locals())
   @apply
   def l():
     def fget(self):
       return self._l
     def fset(self):
       raise AttributeError("%s.l is readonly" % self)
   def __init__(self, m):
     self.m = m

Now if you absolutely insist on using custom descriptors, you'll need 
two of them: one to manage access to s, and the second to manage access 
to l (which btw is a very bad name):

class DescS(object):
   def __init__(self, val):
     self._default = val

   def __set___(self, obj, val):
     obj._s = val
     obj._l = len(val)

   def __get__(self, obj, cls):
     if obj is None:
       return self # or self._default, or whatever
     try:
       return obj._s
     except AttributeError:
       return self._default


class DescL(object):
   def __init__(self, descS):
     self._descS = descS
   def __get__(self, obj, cls):
     if obj is None:
       return self # or self._default, or whatever
     try:
       return obj._l
     except AttributeError:
       return len(self._descS._default)


class Test(object):
   m = DescS('abc')
   l = DescL(m)

(nb : not tested)



More information about the Python-list mailing list