[Python-Dev] assymetry in descriptor behavior

David Abrahams dave@boost-consulting.com
Sat, 22 Feb 2003 17:12:13 -0500


In trying to support user requests for C++-like behavior of wrapped
static data members, I noticed the following little assymetry:

  >>> #define a property class
  ... class Prop(object):
  ...     def __get__(self, obj, type=None):
  ...         print '__get__', (self, obj, type)
  ...         return 'value'
  ...
  ...     def __set__(self, obj, type=None):
  ...         print '__set__', (self, obj, type)
  ...
  ...     def __delete__(self, obj, type=None):
  ...         print '__delete__', (self, obj, type)
  ...
  >>> # use it in a class Y
  ... class Y(object):
  ...     x = Prop()
  ...
  >>> a = Y()

  >>> a.x # all accesses to a.x are intercepted
  __get__ (<__main__.Prop object at 0x00877BC8>, <__main__.Y object at 0x00878108>, <class '__main__.Y'>)
  'value'
  >>> a.x = 42 
  __set__ (<__main__.Prop object at 0x00877BC8>, <__main__.Y object at 0x00878108>, 42)

  >>> Y.x # Prop intercepts reads of the class attribute
  __get__ (<__main__.Prop object at 0x00877BC8>, None, <class '__main__.Y'>)
  'value'
  >>> Y.x = 1 # But not assignments
  >>> Y.x
  1

  >>> class mc(object.__class__): # to intercept Y.x assignment
  ...     x = Prop()              # I have to define this
  ...
  >>> class Y(object):
  ...     __metaclass__ = mc
  ...
  >>> Y.x # now all accesses to Y.x are intercepted
  __get__ (<__main__.Prop object at 0x00876AB8>, <class '__main__.Y'>, <class '__main__.mc'>)
  'value'
  >>> Y.x = 1
  __set__ (<__main__.Prop object at 0x00876AB8>, <class '__main__.Y'>, 1)
  >>> a = Y() # But not accesses to a.x
  >>> a.x
  Traceback (most recent call last):
    File "<stdin>", line 1, in ?
  AttributeError: 'Y' object has no attribute 'x'
  >>>

As you can see, the only way to intercept assignment to Y.x is to
stick a property Y's class, i.e. the metaclass (or to modify
__setattr__ in the metaclass, but it amounts to the same thing).

In C++, a mutable static data member can be modified via the class

   Y::x = 1;

or an instance of the class

   a.x = 1;

I notice that Python supports this sort of dual access for reading
attributes and calling static functions, but getting that behavior for
mutable attributes seems unreasonably difficult: I need a property in
the metaclass *and* in the class.

1. To throw out a straw-man suggestion, what about adding an
   additional protocol __set2__ which, if found, will be called
   instead of __set__ both for reading _and_ writing attributes on the
   class?

2. What are the optional type=None arguments for?  It seems as though
   only the middle argument (obj) is ever None.  I just copied this
   protocol out of descrintro.html

3. Is there documentation for __delete__ anywhere?

-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com