Making class attributes non-case-sensitive?

Rafe rafesacks at gmail.com
Tue Oct 14 00:35:36 EDT 2008


I'm not sure what went wrong with the formatting in my last post. my
code is under 80 characters wide. Here is a more narrow copy and
paste...

class DelegationWrapper(object):
    """
    This is a new-style base class that allows python to
    extend, or override attributes of a given X3DObject.

    :parameters:
        obj : object instance
            If this class (or a sub-class of this class) do
            not have an attribute, this wrapped object will
            be checked before failing.
    """
    def __init__(self, obj):
        """
        Store the object to delegate to.
        """
        self.__obj = obj


    def __repr__(self):
        """
        Makes the object's name the string representation
        of the object, just like XSI does.
        """
        return str(self.__obj.name)


    def __getattr__(self, name):
        """
        Tries to delegate any attribute calls not found in
        this class to the X3DObject.
        """
        # Try to delegate to the 3DObject.
        obj = self.__dict__["__obj"]
        try:
            return obj.__getattr__(name)
        except:
            pass

        # Raise an attribute error (Python requires this
        # to avoid problems)
        className = self.__class__.__name__
        msg = "%s has no attribute '%s'." % (className, name)
        raise AttributeError(msg)


    def __setattr__(self, name, val):
        """
        Tries to delegate any attribute assignment not found
        in this class to the X3DObject.
        """
        # This allows sub-classes to add "private" attributes
        # freely. dir is checked insteaf od __dict__ because
        # it contains bound attributes not available in the
        # instance __dict__.
        if name in dir(self) or name.startswith("_"):
            object.__setattr__(self, name, val)
            return

        # Try to delegate to the X3DObject.
        try:
            self.__dict__["__obj"].__setattr__(name, val)
            return
        except TypeError, err:
            raise TypeError(err)
        except AttributeError:
            pass   # raised later
        except Exception, err:
            raise Exception(err)

        # Don't allow addition of new 'public' attributes
        # with AttributeError
        className = self.__class__.__name__
        msg = "%s has no attribute '%s'." % (className, name)
        raise AttributeError(msg)


    @property
    def name(self):
        """
        This doesn't do anything here, but in my real code it
        does. The problem is, if the user types 'Name' this
        will be bypassed.
        """
        return self.__obj.Name


- Rafe




On Oct 14, 11:29 am, Rafe <rafesa... at gmail.com> wrote:
> I really appreciate the replies. I hope you gyus will stick with me
> through one more round.
>
> super(C, self).__setattr__(attr.lower(), value)
>
> Unfortunately, this will not work because an attribute name such as
> "getObject" is legal (I'll explain the convention in a moment.) I
> think I would have to loop over all attributes and force both sides of
> the compare to lower case to test for a match.
>
> just skip ahead to the example code if you don't want more confusing
> background ;)
>
> Bear with me while I try to explain.
>
> Basically, I am working with this application like (I think) any
> application would work through a COM object. That said, I can access
> python from within the application as well because it is a kind of dev
> environment. 3D applications are blended GUI and development
> environment and users are expected to use it through both the API and
> the GUI. What may seem strange to most people here, is that you will
> get hard-core programmers and surface-level users (and everything in
> between, like me) working and/or developing in the same environment.
> These 3D software applications are quite large and complex.
>
> The application is called "Softimage|XSI", commonly called "XSI". It
> is a 3D application. Most companies will licenses the software but
> then build layers on top of it for pipeline productivity and
> communication reasons. So, it is standard for a user of the
> application to also write scripts or more complex OO models. I
> mentioned it was written during the brief period of time where
> Softimage was owned by Microsoft because I thought there might be some
> precedence for the case sensitivity issues. It was not written by
> Microsoft engineers directly, but they did enforce *some* standards.
>
> The common naming convention in XSI is (using PEP008 terminology)
> "CapitalizedWords" for objects and functions/methods, and "mixedCase"
> for variables: This is from the C++ API:
>
> C++ Example: connecting to XSI
>     // gets the application object, which you can use to communicate
> with XSI
>     Application app;
>     app.LogMessage( "Welcome to XSI!" );
>
> C++ Example: creating an X3DObject
>     // returns the reference root object
>     namespace XSI;
>     Application app;
>     CRef rootRef = app.GetActiveSceneRoot();
>
>     // create object with a reference object
>     X3DObject rootObj(rootRef);
>
> The python version of the above C++ example looks like this.
>     from win32com.client.dynamic import Dispatch
>     XSI = Dispatch('XSI.Application').Application
>     XSI.LogMessage("Welcome to XSI!")
>     root = XSI.ActiveSceneRoot
>
> As for the convention I chose, it is right out of PEP008.
> "Function Names
>
>       Function names should be lowercase, with words separated by
> underscores
>       as necessary to improve readability.
>
>       mixedCase is allowed only in contexts where that's already the
>       prevailing style (e.g. threading.py), to retain backwards
> compatibility."
>
> Too keep my code in line with XSI's API, I took this second part to
> hear. All other conventions are in line with PEP008 I believe. Lastly,
> though I can see how this might sound confusing, I stick with the XSI
> API convension exactly when accessing it directly("CapitalizedWords"),
> but anything I write is PEP008 with mixedCase.
>
> The most important part of all this though is my original issue. For
> some reason, the XSI implementation is not case sensitive. This
> works!...
>
> from win32com.client.dynamic import Dispatch
> XSI = Dispatch('XSI.Application').Application
> XSI.LogMessage("Welcome to XSI!")
> XSI.loGmeSSAGE("Welcome to XSI!")
>
> This is probably totally usless info for this discussion (like I
> haven't already provided enough of that!), but the XSI API, or object
> model, is a little like a complex XML DOM tree...
>
> obj = XSI.Dictionary.GetObject("my3DObject")
> children = obj.Children
> for child in children:
>     XSI.LogMessage(child.Name)
>
> To wrap and override the 'name' attribute I use this class. (Note I
> had some trouble with __setattr__ but this IS stable. I welcome
> comments as this is probably one of the most confusing things to work
> with for new python users.)
>
> class DelegationWrapper(object):
>     """
>     This is a new-style base class that allows python to extend, or
> override
>     attributes of a given X3DObject.
>
>     :parameters:
>         obj : object instance
>             If this class (or a sub-class of this class) do not have
> an
>             attribute, this wrapped object will be checked before
> failing.
>     """
>     def __init__(self, obj):
>         """
>         Store the object to delegate to.
>         """
>         self.__obj = obj
>
>     def __repr__(self):
>         """
>         Makes the object's name the string representation of the
> object, just
>         like XSI does.
>         """
>         return str(self.__obj.name)
>
>     def __getattr__(self, name):
>         """
>         Tries to delegate any attribute calls not found in this class
> to the
>         X3DObject.
>         """
>         # Try to delegate to the 3DObject.
>         obj = self.__dict__["__obj"]
>         try:
>             return obj.__getattr__(name)
>         except:
>             pass
>
>         # Raise an attribute error (Python requires this to avoid
> problems)
>         className = self.__class__.__name__
>         raise AttributeError("%s has no attribute '%s'." % (className,
> name))
>
>     def __setattr__(self, name, val):
>         """
>         Tries to delegate any attribute assignment not found in this
> class to
>         the X3DObject.
>         """
>         # This allows sub-classes to add "private" attributes freely.
>         # dir is checked insteaf od __dict__ because it contains bound
>         # attributes not available in the instance __dict__.
>         if name in dir(self) or name.startswith("_"):
>             object.__setattr__(self, name, val)
>             return
>
>         # Try to delegate to the X3DObject.
>         try:
>             self.__dict__["__obj"].__setattr__(name, val)
>             return
>         except TypeError, err:
>             raise TypeError(err)
>         except AttributeError:
>             pass   # raised later
>         except Exception, err:
>             raise Exception(err)
>
>         # Don't allow addition of new 'public' attributes with
> AttributeError
>         className = self.__class__.__name__
>         raise AttributeError("%s has no attribute '%s'." % (className,
> name))
>
>     @property
>     def name(self):
>         """
>         This doesn't do anything here, but in my real code it does.
> The
>         problem is, if the user types 'Name' this will be bypassed.
>         """
>         return self.__obj.Name
>
> So is iterating through dir() to force both the members of dir(), and
> the requested attribute name, to lower case for a comparison, really
> the easiest way?
>
> Thanks again for sticking with me. I hope I didn't add to the
> confusion. What I learn I will of course pass on.
>
> - Rafe
>
> On Oct 14, 12:14 am, Matimus <mccre... at gmail.com> wrote:
>
> > On Oct 13, 4:08 am, Rafe <rafesa... at gmail.com> wrote:
>
> > > Just so I don't hijack my own thread, the issue is 'how to wrap an
> > > object which is not case sensitive'.
>
> > > The reason I am stuck dealing with this?... The application's API is
> > > accessed through COM, so I don't know if I can do anything but react
> > > to what I get. The API was written while the app (Softimage|XSI - one
> > > of 3 leading 3D applications for high-end visual effects) was owned by
> > > Microsoft. I'm not sure if it is standard for Microsoft or just the
> > > way this app was implemented (perhaps because under-users were
> > > scripting in VBscript which is not case sensitive).
>
> > > XSI allows many languages to be used via COM, even from within the
> > > software (there are built-in code editors). In the early days,
> > > VBScript was the most common scripting language used while anything
> > > more hard-core was done in C++ (of course the C implementation is case
> > > sensitive - well as far as I know). Then JScript became the most
> > > common, now Python is considered standard.
>
> > > Anyway, the standard practice is to use mixed-case, so I need to
> > > adhere to it as the resulting framework I am creating needs to be
> > > intuitive to use (my end-user is still writing code. It's an API for
> > > an API I guess...)
>
> > > I don't *think* I need to worry too much about performance because I'm
> > > not doing any serious processing, this is more about convention
> > > enforcement and quality control rather than number crunching. I might
> > > try to write something generic which gets executed by the wrappers
> > > __getattr__ and __setattr__, but I was hoping for some nifty
> > > workaround, maybe in the form of a decorator or something? Again...
> > > any ideas?
>
> > > Cheers,
>
> > > - Rafe
>
> > > On Oct 13, 4:15 pm, "Diez B. Roggisch" <de... at nospam.web.de> wrote:
>
> > > > Rafe wrote:
> > > > > Hi,
>
> > > > > I'm working within an application (making a lot of wrappers), but the
> > > > > application is not case sensitive. For example, Typing obj.name,
> > > > > obj.Name, or even object.naMe is all fine (as far as the app is
> > > > > concerned). The problem is, If someone makes a typo, they may get an
> > > > > unexpected error due accidentally calling the original attribute
> > > > > instead of the wrapped version. Does anyone have a simple solution for
> > > > > this?
>
> > > > > I can protect against some cases just by making an 'alias':
> > > > > class AClass(object):
> > > > >     def name(self):
> > > > >         print "hello"
>
> > > > >     Name = name
>
> > > > > ...but this doesn't protect against typos, it gets more complicated
> > > > > with multi-word attribute names, and it makes my epydocs confusing to
> > > > > read since all spelling versions are shown (I AM concerned about my
> > > > > docs being clear, but not as much as stopping typo related errors).
>
> > > > > I thought about using my wrapper's __getattr__ and __setattr__, but I
> > > > > I am concerned about the overhead of every delegated attribute call
> > > > > running a search and compare (<paramName>.lower() based compare?).
>
> > > > > Any ideas or precedence?
>
> > > > Ideas? Don't do that...
>
> ...
>
> read more »




More information about the Python-list mailing list