[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)