peps: b4938632c5c2 (original) (raw)
Mercurial > peps
changeset 28:b4938632c5c2
Added content. First real version of this PEP
Paul Prescod prescod@prescod.net | |
---|---|
date | Fri, 21 Jul 2000 21:14:26 +0000 |
parents | e40b23dd57b1 |
children | 5a659d181adf |
files | pep-0213.txt |
diffstat | 1 files changed, 191 insertions(+), 0 deletions(-)[+] [-] pep-0213.txt 191 |
line wrap: on
line diff
--- a/pep-0213.txt +++ b/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 [](#l1.9)
in extension modules to "trap" when an instance's client code [](#l1.10)
attempts to set an attribute and execute code instead. In other [](#l1.11)
words it is possible to allow users to use attribute assignment/[](#l1.12)
retrieval/deletion syntax even though the underlying implementation [](#l1.13)
is doing some computation rather than directly modifying a[](#l1.14)
binding.[](#l1.15)
This PEP describes a feature that makes it easier, more efficient[](#l1.17)
and safer to implement these handlers for Python instances.[](#l1.18)
You have a deployed class that works on an attribute named[](#l1.24)
"stdout". After a while, you think it would be better to[](#l1.25)
check that stdout is really an object with a "write" method[](#l1.26)
at the moment of assignment. Rather than change to a[](#l1.27)
setstdout method (which would be incompatible with deployed[](#l1.28)
code) you would rather trap the assignment and check the[](#l1.29)
object's type.[](#l1.30)
You want to be as compatible as possible with an object [](#l1.34)
model that has a concept of attribute assignment. It could[](#l1.35)
be the W3C Document Object Model or a particular COM [](#l1.36)
interface (e.g. the PowerPoint interface). In that case[](#l1.37)
you may well want attributes in the model to show up as[](#l1.38)
attributes in the Python interface, even though the [](#l1.39)
underlying implementation may not use attributes at all.[](#l1.40)
A user wants to make an attribute read-only.[](#l1.44)
- 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.
- class foo:
def __setattr__( self, name, val ):[](#l1.56)
if name=="readonlyattr":[](#l1.57)
raise TypeError[](#l1.58)
elif name=="readonlyattr2":[](#l1.59)
raise TypeError[](#l1.60)
...[](#l1.61)
else:[](#l1.62)
self.__dict__["name"]=val[](#l1.63)
This has the following problems:[](#l1.65)
1. The creator of the method must be intimately aware of whether[](#l1.67)
somewhere else in the class hiearchy __setattr__ has also been[](#l1.68)
trapped for any particular purpose. If so, she must specifically[](#l1.69)
call that method rather than assigning to the dictionary. There[](#l1.70)
are many different reasons to overload __setattr__ so there is a[](#l1.71)
decent potential for clashes. For instance object database[](#l1.72)
implementations often overload setattr for an entirely unrelated[](#l1.73)
purpose.[](#l1.74)
2. The string-based switch statement forces all attribute handlers [](#l1.76)
to be specified in one place in the code. They may then dispatch[](#l1.77)
to task-specific methods (for modularity) but this could cause[](#l1.78)
performance problems.[](#l1.79)
3. Logic for the setting, getting and deleting must live in [](#l1.81)
__getattr__, __setattr__ and __delattr__. Once again, this can be[](#l1.82)
mitigated through an extra level of method call but this is [](#l1.83)
inefficient.[](#l1.84)
- class x:
def __attr_XXX__(self, op, val ):[](#l1.92)
if op=="get":[](#l1.93)
return someComputedValue(self.internal)[](#l1.94)
elif op=="set":[](#l1.95)
self.internal=someComputedValue(val)[](#l1.96)
elif op=="del":[](#l1.97)
del self.internal[](#l1.98)
- Semantics
Attribute references of all three kinds should call the method.[](#l1.108)
The op parameter can be "get"/"set"/"del". Of course this string[](#l1.109)
will be interned so the actual checks for the string will be[](#l1.110)
very fast.[](#l1.111)
It is disallowed to actually have an attribute named XXX in the[](#l1.113)
same instance as a method named __attr_XXX__.[](#l1.114)
An implementation of __attr_XXX__ takes precedence over an[](#l1.116)
implementation of __getattr__ based on the principle that[](#l1.117)
__getattr__ is supposed to be invoked only after finding an[](#l1.118)
appropriate attribute has failed.[](#l1.119)
An implementation of __attr_XXX__ takes precedence over an[](#l1.121)
implementation of __setattr__ in order to be consistent. The[](#l1.122)
opposite choice seems fairly feasible also, however. The same[](#l1.123)
goes for __del_y__.[](#l1.124)
- 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__[](#l1.131)
method (pointer to a method object[](#l1.132)
- 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 I_have_computed_attributes flag up to date as attributes[](#l1.171)
are added and removed from the instance's dictionary. This is[](#l1.172)
consistent with current Python. If you add a __setattr__ method[](#l1.173)
to an object after it is in use, that method will not behave as[](#l1.174)
it would if it were available at "compile" time. The dynamism is[](#l1.175)
arguably not worth the extra implementation effort. This snippet[](#l1.176)
demonstrates the current behavior:[](#l1.177)
+ + >>> def prn(*args):print args + >>> class a: + ... setattr=prn
>>> a().foo=5[](#l1.182)
- (<__main__.a instance at 882890>, 'foo', 5) +
2. Assignment to __dict__["XXX"] can overwrite the attribute[](#l1.190)
- access handler for attr_XXX. Typically the access handlers will
store information away in private __XXX variables[](#l1.192)
3. An attribute access handler that attempts to call setattr or getattr[](#l1.194)
on the object itself can cause an infinite loop (as with __getattr__)[](#l1.195)
Once again, the solution is to use a special (typically private) [](#l1.196)
variable such as __XXX.[](#l1.197)