[Python-Dev] Pre-PEP: Attribute Access Handlers v2

Paul Prescod paul@prescod.net
Fri, 21 Jul 2000 11:25:39 -0500


I don't have a PEP number for this yet but I wanted to go through
another round of comments after changing the mechanism according to Don
Beaudry's suggestions.

The name has been changed because every attribute is computed at some
point or another. The important thing is that we are defining methods
that trap sets/gets to attributes.

PEP: ???
Title: Attribute Access Handlers
Version: $Revision: 1.0 $
Owner: paul@prescod.net
Python-Version: 2.0
Status: Incomplete

Introduction

     It is possible (and even relatively common) in Python code and 
     in extension modules to "trap" when an instance's client code 
     attempts to set an attribute and execute code instead. In other 
     words it is possible to allow users to use attribute assignment/
     retrieval/deletion syntax even though the underlying implementation 
     is doing some computation rather than directly modifying a
     binding.

     This PEP describes a feature that makes it easier, more efficient
     and safer to implement these handlers in Python than it is today.

Justification

    Scenario 1:

        You have a deployed class that works on an attribute named
        "stdout". After a while, you think it would be better to
        check that stdout is really an object with a "write" method
        at the moment of assignment. Rather than change to a
        setstdout method (which would be incompatible with deployed
        code) you would rather trap the assignment and check the
        object's type.

    Scenario 2:

        You want to be as compatible as possible with an object 
        model that has a concept of attribute assignment. It could
        be the W3C Document Object Model or a particular COM 
        interface (e.g. the PowerPoint interface). In that case
        you may well want attributes in the model to show up as
        attributes in the Python interface, even though the 
        underlying implementation may not use attributes at all.

    Scenario 3:

        A user wants to make an attribute read-only.

    In short, this feature allows programmers to separate the 
    interface of their module from the underlying implementation
    for whatever purpose. Again, this is not a new feature but
    merely a new syntax for an existing convention.

Syntax
 
    Special methods should declare themselves with declarations 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

    Client code looks like this:

    fooval=x.foo
    x.foo=fooval+5
    del x.foo

 Semantics
 
     Attribute references of all three kinds should call the method.
     The op parameter can be "get"/"set"/"del". Of course this string
     will be interned so the actual checks for the string will be
     very fast.
 
     It is disallowed to actually have an attribute named XXX in the
     same instance as a method named __attr_XXX__.

     An implementation of __attr_XXX__ takes precedence over an
     implementation of __getattr__ based on the principle that
     __getattr__ is supposed to be invoked only after finding an
     appropriate attribute has failed.

     An implementation of __attr_XXX__ takes precedence over an
     implementation of __setattr__ in order to be consistent. The
     opposite choice seems fairly feasible also, however. The same
     goes for __del_y__.

Proposed Implementation
 
    There is a new object called a computed attribute object. It has
    three attributes: get, set, delete. In PyClass_New, methods of
    the appropriate form will be detected and converted into objects
    (just like unbound method objects).  Matching methods go in the same
    computed attribute object and missing methods are replaced with a
    stub that throws the TypeError. If there are any computed attributes
    at all, a flag is set. Let's call it "I_have_computed_attributes"
    for now.
 
    A get proceeds as usual until just before the object is returned.
    In addition to the current check whether the returned object is a
    method it would also check whether a returned object is a computed
    attribute.  If so, it would invoke the getter method and return
    the value. To remove a computed attribute object you could directly
    fiddle with the dictionary.
 
    A set proceeds by checking the "I_have_computed_attributes" flag. If
    it is not set, everything proceeds as it does today. If it is set
    then we must do a dictionary get on the requested object name. If it
    returns a computed method attribute then we call the setter function
    with the value. If it returns any other object then we discard the
    result and continue as we do today. Note that having a computed
    attribute will affect attribute setting performance for all sets
    on a particular instance, but no more so than today. Gets are
    more efficient than they are today with __getattr__.
 
    The I_have_computed_attributes flag is intended to eliminate the
    performance degradation of an extra "get" per "set" for objects not
    using this feature. You might note that I have not proposed any
    logic to keep this flag up to date as attributes are added and 
    removed from the instance's dictionary. This is consistent 
    with current Python. If you add a __setattr__ method to an 
    object after it is in use, that method will not behave as it 
    would if it were available at "compile" time. The dynamism is
    arguably not worth the extra implementation effort.
 
    The implementation of delete is analogous to the implementation
    of set.
-- 
 Paul Prescod - Not encumbered by corporate consensus
"Hardly anything more unwelcome can befall a scientific writer than 
having the foundations of his edifice shaken after the work is 
finished.  I have been placed in this position by a letter from 
Mr. Bertrand Russell..." 
 - Frege, Appendix of Basic Laws of Arithmetic (of Russell's Paradox)