New PEP: Attribute Access Handlers

Christian Tanzer tanzer at swing.co.at
Sun Jul 23 03:56:08 EDT 2000


> Paul Prescod <paul at prescod.net> wrote:
> 
> http://python.sourceforge.net/peps/pep-0213.html
> 
> PEP: 213
> Title: Attribute Access Handlers
> Version: $Revision: 1.3 $
> Owner: paul at prescod.net (Paul Prescod)
> Python-Version: 2.0
> Status: Incomplete
(snip)
> Current Solution
> 
>     To make some attributes read-only:
> 
>     class foo:
>        def __setattr__( self, name, val ):
>           if name=="readonlyattr":
>              raise TypeError
>           elif name=="readonlyattr2":
>              raise TypeError
>           ...
>           else:
>              self.__dict__["name"]=val
> 
>      This has the following problems:
> 
>      1. The creator of the method must be intimately aware of whether
>         somewhere else in the class hiearchy __setattr__ has also been
>         trapped for any particular purpose. If so, she must specifically
>         call that method rather than assigning to the dictionary. There
>         are many different reasons to overload __setattr__ so there is a
>         decent potential for clashes. For instance object database
>         implementations often overload setattr for an entirely unrelated
>         purpose.
> 
>      2. The string-based switch statement forces all attribute handlers 
>         to be specified in one place in the code. They may then dispatch
>         to task-specific methods (for modularity) but this could cause
>         performance problems.
> 
>      3. Logic for the setting, getting and deleting must live in 
>         __getattr__, __setattr__ and __delattr__. Once again, this can
>         be mitigated through an extra level of method call but this is 
>         inefficient.
> 
> Proposed Syntax
>  
>     Special methods should declare themselves with definitions of the
>     following form:
> 
>     class x:
>         def __attr_XXX__(self, op, val ):
>             if op=="get":
>                 return someComputedValue(self.internal)
>             elif op=="set":
>                 self.internal=someComputedValue(val)
>             elif op=="del":
>                 del self.internal
(snip)

I agree that 1. and 2. are sometimes problems, but IMHO point 3. is a
good thing, not a problem.

In general, get/set/del are not related. For instance:

    $ dir ~/ttt/ttptools/*/*.py | wc -l
        426
    $ grep '  def __setattr__' ~/ttt/ttptools/*/*.py | wc -l
          5
    $ grep '  def __getattr__' ~/ttt/ttptools/*/*.py | wc -l
         44
    $ grep '  def __delattr__' ~/ttt/ttptools/*/*.py | wc -l
          0

In short, folding get/set/del into one function sucks. Besides, it
doesn't really help when you want to overload just get, but would like
to keep the set/del of the ancestor.

To disentangle the accessors of different attributes, I would suggest
to use a dictionary of attribute-specific accessor functions.

For instance:

    class Foo :
        def _set_bar  (self, name, value) : ...
        def _set_baz  (self, name, value) : ...
        def _dont_set (self, name, value) : raise Readonly_Attribute, name
        __attr_setters__ = { "bar" : _set_bar
                           , "baz" : _set_baz
                           , "ro1" : _dont_set
                           , "ro2" : _dont_set
                           }

        def __setattr__ (self, name, value):
            if self.__attr_setters__.has_key (name) :
                self.__attr_setters__ [name] (self, name, value)
            else :
                ...

    class Bar (Foo) :
        def _set_baz  (self, name, value) : ...

    def dict (* dicts, ** kw) :
        result = {}
        for d in dicts + (kw, ) : result.update (d)
        return result

    class Baz (Bar) :
        __attr_setters__ = dict (Bar.__attr_setters__, baz = Bar._dont_set)

This tackles problem 1 and half of problem 2.
      
If the performance side of problem 2 is really an issue, move the
checking of `__attr_setters__/__attr_getters__/__attr_deleters__' from
`__setattr__/__getattr__/__delattr__' into the interpreter (other
names for the magic dictionaries might be useful then). 

-- 
Christian Tanzer                                         tanzer at swing.co.at
Glasauergasse 32                                       Tel: +43 1 876 62 36
A-1130 Vienna, Austria                                 Fax: +43 1 877 66 92





More information about the Python-list mailing list