New PEP: Attribute Access Handlers
Bjorn Pettersen
bjorn at roguewave.com
Sat Jul 22 00:48:21 EDT 2000
Justification, Scenario 4:
An existing class has a string attribute, however the most common
operation on it is appending to the end. Through the profiler you find
that this operation is taking up 95% of the applications time. You want
to change the implementation to cStringIO for efficiency (total
execution time for your application goes from 90 seconds to 3 secs).
True story :-)
Perhaps you should add a few words about why you chose __attr_XXX__
instead of __set/get/del_XXX__? Would both of them work for
introspection for e.g. a GUI builder? I seem to remember that JavaBeans
<shudder> looks for the presence of set/get methods to make attributes
available, I'm not sure if Python could look at an objects __dict__ for
that information under this proposal?
-- bjorn
Paul Prescod 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
>
> Introduction
>
> It is possible (and even relatively common) in Python code and
> in extension modules to "trap" when an object'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 or
> reporting
> a binding.
>
> This PEP describes a feature that makes it easier, more efficient
> and safer to implement these handlers in Python class instances.
>
> Justification
>
> Scenario 1:
>
> You have a deployed class that works on an attribute named
> "stdout". After a year, 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 and more robust syntax for something that can
> already be implemented.
>
> 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
>
> Client code looks like this:
>
> inst=x()
> fooval=inst.XXX
> inst.XXX=fooval+5
> del x.XXX
>
> 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 dictionary as a method named __attr_XXX__.
> If both are declared in a class then the last one declared
> replaces the other one.
>
> 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 as a
> last resort after an appropriate attribute lookup 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 type called an attribute access handler.
> Objects of this type have the following attributes:
>
> name (e.g. XXX, not __attr__XXX__
> method (pointer to a method object
>
> Class construction
>
> In PyClass_New, methods of the appropriate form will be
> detected and converted into objects (just like unbound method
> objects). These are stored in the class __dict__ under the name
> XXX. The original method is stored as an unbound method under
> its original name.
>
> If there are any attribute access handlers in an class at all,
> a flag is set. Let's call it "I_have_computed_attributes" for
> now. Derived classes inherit the flag from base classes.
>
> Instance construction
>
> Instances inherit the "I_have_computed_attributes" flag from
> classes. No other changes to PyInstance_New are anticipated.
>
> Property fetching
>
> At the layer, Python instance attribute lookup is always done
> through PyInstance_getattr.
>
> 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 an access handler. If so, it would invoke the getter method
> and return the value. To remove an attribute access handler you
> could directly fiddle with the dictionary. Gets should be more
> efficient than they are today with __getattr__ and no different
> for classes that do not use the feature at all.
>
> 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
> attribute name. If it returns an attribute access handler 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 an attribute access handler will mildly
> affect attribute "setting" performance for all sets on a
> particular instance, but no more so than today, using
> __setattr__.
>
> 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. Checking this flag should have miniscule
> performance implications for all objects.
>
> The implementation of delete is basically identical to the
> implementation of set.
>
> Caveats
>
> 1. You might note that I have not proposed any logic to keep
> the I_have_computed_attributes 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. This snippet
> demonstrates the current behavior:
>
> >>> def prn(*args):print args
> >>> class a:
> ... __setattr__=prn
> >>> a().foo=5
> (<__main__.a instance at 882890>, 'foo', 5)
>
> >>> class b: pass
> >>> bi=b()
> >>> bi.__setattr__=prn
> >>> b.foo=5
>
> 2. Assignment to __dict__["XXX"] can overwrite the attribute
> access handler for __attr_XXX__. Typically the access handlers
> will store information away in private __XXX variables
>
> 3. An attribute access handler that attempts to call setattr or
> getattr on the object itself can cause an infinite loop (as with
> __getattr__) Once again, the solution is to use a special
> (typically private) variable such as __XXX.
>
> --
> Paul Prescod - Not encumbered by corporate consensus
> New from Computer Associates: "Software that can 'think', sold by
> marketers who choose not to."
>
> --
> http://www.python.org/mailman/listinfo/python-list
More information about the Python-list
mailing list