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