Added content. First real version of this PEP
This commit is contained in:
parent
37a9ef08cc
commit
e743584658
191
pep-0213.txt
191
pep-0213.txt
|
@ -6,6 +6,197 @@ 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 for Python instances.
|
||||
|
||||
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.
|
||||
|
||||
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 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 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
|
||||
|
||||
In PyClass_New, methods of
|
||||
the appropriate form will be detected and converted into objects
|
||||
(just like unbound method objects). If there are any attribute access
|
||||
handlers in an instance at all, a flag is set. Let's call
|
||||
it "I_have_computed_attributes" for now. Derived classes inherit
|
||||
the flag from base classes. Instances inherit the flag from
|
||||
classes.
|
||||
|
||||
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.
|
||||
|
||||
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 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__. 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. Checking this flag should have miniscule
|
||||
performance implications for all objects.
|
||||
|
||||
The implementation of delete is analogous 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.
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
|
|
Loading…
Reference in New Issue