I will be proposing this PEP
Collin Stocks
collinstocks at gmail.com
Fri Jan 5 17:59:11 EST 2007
Attached is a PEP which I will be proposing soon. If you have any questions,
comments, or suggestions, please email them to me with the subject "Adding
Built-in Class Attributes PEP"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-list/attachments/20070105/25805a38/attachment.html>
-------------- next part --------------
PEP: XXX
Title: Adding Built-in Class Attributes
Version: $Revision$
Last-Modified: $Date$
Author: Collin Stocks <subscriber 1 2 3 at gmail dot com>
Status: Draft
Type: Standards Track
Content-Type: text/plain
Created: 03-Jan-2007
Python-Version: 2.6
Post-History:
Abstract
Python currently allows users to create their own __setattr__,
__getattr__ and __delattr__ member functions inside a class. This is
all well and dandy, but it causes programmers to have to find a
different way to set, get, and delete attributes within those
functions. In __delattr__ and __getattr__, this usually is not a
problem, because you can just get or delete those from __dict__, the
built-in dictionary in a class. __setattr__ is more of a problem,
because, if self.my_attr does not exist,
self.__dict__['my_attr']="some value" raises an exception. This PEP
proposes to add __setattr__, __getattr__ and __delattr__ as built-in
member functions of all Python class objects.
Rationale
Many programs that use classes need to create their own __setattr__,
__getattr__ and __delattr__ member functions. If not done carefully,
this can cause recursion errors (something that increases the learning
curve for newbies). This is also not very object oriented. Really, does
it make sense to use objects of two different types to refer to the
same object? This PEP does not propose to remove __dict__, just to add
a more sensical way to access the attributes of a class. This will
prevent recursion, make Python code make more sense and introduce a
more object oriented and correct way of handling class attributes.
Specification:
Any class created by a Python file will have the __setattr__,
__getattr__ and __delattr__ member functions built into it upon
initiation. This would allow programmers to create copies of these
functions, and then create their own, which call on the copies in order
to change attributes. For example, suppose you wanted to make the
attribute readOnlyAttr read-only:
In the current version of Python, you must do something like this:
class blah:
def __init__(self):
self.readOnlyAttr=1
def __setattr__(self,name,value):
try:
assert name=="readOnlyAttr"
print 1
self.__dict__[name]
print 2
raise TypeError, "Cannot change a readonly attribute"
except KeyError:
self.__dict__[name]=value
except AssertionError:
self.__dict__[name]=value
The downside to this is obvious: This is an enormous amount of code to
do a simple thing, and must be cleverly thought out. Most would say
that this is not Pythonic. Here is what that code would look like
following this proposal:
class blah:
def __init__(self):
self.readOnlyAttr=1
self.save_setattr=self.__setattr__ #makes a copy of the
# previous, built-in __setattr__()
def __setattr__(self,name,value): #this function is created
# after __init__() has been called
if name=="readOnlyAttr":
raise TypeError, "Cannot change a readonly attribute"
else:
self.save_setattr(name,value)
This code is much smaller and more readable. It also does not use
__dict__[], which is a dictionary representation of the class. Since
blah().__dict__ and blah() refer to the same object in the current
version of Python, we are not able to take full advantage of object
orientation, because this causes class objects to really have two
different types. This PEP does not propose to remove __dict__[] (maybe
in Python 3000 __dict__[] will be removed); It just proposes a way to
take a fuller advantage of object orientation.
Another example: Suppose you wanted to create your own __setattr__()
member function, but you were deriving your class from another class
that may or may not have that member function already. Suppose also,
that you want to use that function after filtering through your own
__setattr__() member function. This is how that might be done in the
current version of Python:
class blah(derivitive):
def __init__(self):
try:
self.save_setattr=self.__setattr__
self.__setattr__=self.new_setattr
except:
self.__setattr__=self.new_setattr
def new_setattr(self,name,value):
#...
#code block
#...
try:
self.save_setattr(name,value)
except:
self.__dict__[name]=value
The obvious downside to this, is again, that it is too long, and it is
not pythonic. It is in fact, quite the reverse: clever. A newbie would
never think up that code! Here is what that code would look like
following this proposal:
class blah(derivitive):
def __init__(self): #this saves a copy of the old __setattr__
# member function, whether it is the original
# built-in one, or the one that was derived
self.save_setattr=self.__setattr__
def __setattr__(self,name,value): #this function is created after
# __init__() has been called
#...
#code block
#...
self.save_setattr(name,value) #this calls the previously
# active __setattr__() member function
This code is also much smaller and more readable! Better yet, it looks
almost exactly the same as the proposed code in the last example! All a
newbie needs to see is one example of how to use the __setattr__()
member function, and s/he is able to make it do whatever s/he wants,
without having to learn all about the try/except blocks, or even much
about manipulating function objects.
This proposal also applies the same concept to the __getattr__() and
__delattr__() member functions. Here is an example class that follows
through with the same concept as before:
class blah(derivitive):
def __init__(self):
self.save_setattr=self.__setattr__
self.save_getattr=self.__getattr__
self.save_delattr=self.__delattr__
def __setattr__(self,name,value): #this function is created after
# __init__() has been called
#...
#code block
#...
self.save_setattr(name,value) #this calls the previously active
# __setattr__() member function
def __getattr__(self,name): #this function is created after
# __init__() has been called
#...
#code block
#...
return self.save_getattr(name) #this calls the previously
# active __getattr__() member function
def __delattr__(self,name): #this function is created after
# __init__() has been called
#...
#code block
#...
self.save_delattr(name) #this calls the previously active
# __delattr__() member function
This is so much simpler than in the current version of python! This
proposal avoids recursion, and gives a more logical and direct way to
get around the problems caused by __getattr__(), __setattr__() and
__delattr__().
Something that has not been mentioned previous to this, but was shown
in the examples, was that __init__() was called BEFORE the functions
__getattr__(), __setattr__() and __delattr__() below it were created.
This sort of turns __init__() into a "magic" member function, as it is
called after everything above it was created, and before the
aforementioned member functions below it were created. This may cause
some incompatability issues which will be resolved later under
<Backwards Compatability>.
Motivation:
In the current version of Python, the problems caused by __getattr__(),
__setattr__() and __delattr__() are a pain to get around. For example,
when they are created, they immediatly overwrite any __getattr__(),
__setattr__() or __delattr__() member functions that were derived from
other classes. As shown in the specification, there are ways to get
around this, but they are very newbie unfriendly; they require a much
more detailed knowledge of the language to find. This PEP proposes a
way in which it takes a minimal knowledge of Python in order to be able
to write classes which take advantage of the __getattr__(),
__setattr__() and __delattr__() member functions.
Also, in the current version of Python, __getattr__(), __setattr__()
and __delattr__() are easy causes of recursion errors. This PEP
proposes a way in which it is more difficult to make the mistake of
causing a recursion error. And, since the code needed to make
__getattr__(), __setattr__() and __delattr__() work is less, if a
recursion error is caused, it is much easier to find and fix than with
the current version of Python.
Backwards Compatability:
Since __init__() in class objects is called before any __getattr__(),
__setattr__() and __delattr__() member functions that appear below it
are created, the following things done by __init__() may not behave as
expected in older programs:
setting an attribute
getting an attribute
deleting an attribute
referencing __getattr__(), __setattr__() and __delattr__() member
functions
These incompatabilities, however, are only a problem if __getattr__(),
__setattr__() and/or __delattr__() have been written below where the
__init__() member function has been written. A solution that comes to
mind is to evaluate, but not run, __init__() to see if there are still
any references to each of the original (meaning built-in or derived)
__getattr__(), __setattr__() and __delattr__() member functions. For
each one, if there still is a reference, then the the original member
function is kept; if there isn't still a reference, then the original
member function is discarded, and the new member function is used in
its place. The __init__() function is then called with the decided
class namespace. This is only a minor incompatability, and others are
welcome to suggest their own solutions to this problem.
Another possible solution to this problem is to simply leave __init__()
alone, and include another special member functions of all classes that
is called immediately after all of the functions above it have been
created, but before any of the code below it has been interpreted. This
function's name would be __setup__(). This is a fitting name, because
it is able to control the way that the class is created, before the
class has initialized. This only leaves a minute incompatability, if
someone for some reason decided in an older program to put the
__setup__() function in their class.
Copyright
This document has been placed in the public domain.
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8
End:
More information about the Python-list
mailing list