[Python-checkins] CVS: python/nondist/peps pep-0213.txt,1.1,1.2
Paul Prescod
python-dev@python.org
Fri, 21 Jul 2000 14:14:28 -0700
Update of /cvsroot/python/python/nondist/peps
In directory slayer.i.sourceforge.net:/tmp/cvs-serv21578/peps
Modified Files:
pep-0213.txt
Log Message:
Added content. First real version of this PEP
Index: pep-0213.txt
===================================================================
RCS file: /cvsroot/python/python/nondist/peps/pep-0213.txt,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -r1.1 -r1.2
*** pep-0213.txt 2000/07/21 20:05:07 1.1
--- pep-0213.txt 2000/07/21 21:14:26 1.2
***************
*** 7,10 ****
--- 7,201 ----
+ 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: