Making class attributes non-case-sensitive?

Rafe rafesacks at gmail.com
Tue Oct 14 06:29:42 CEST 2008


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...
>
> > > Seriously: where does that code come from, who's typing it? If it is python,
> > > then make people follow python's rules. If it is some sort of homebrewn
> > > language you map to python, adapt the mapper to enforce lower-case and make
> > > all your properties lower case.
>
> > > Diez
>
> So, this application you are writing for allows you to script/write
> callbacks in python? They are then somehow accessable through a COM
> interface exposed by the application? You are worried that someone
> using the application will run into case-sensitivity if they access
> the code written in python?
>
> There isn't much overhead with __getattr__ since it is _only_ called
> if the initial look-up didn't find the name. You can do something like
> this:
>
> class C(object):
>     def __init__(self, ...):
>         ...
>         self._lookingup = False
>         ...
>
>     # If this is re-entered while already looking up a value,
>     # then we know that there is a problem. Not thread safe.
>     def __getattr__(self, attr):
>         try:
>             if self._lookingup:
>                 raise AttributeError("'C' object has no attribute
> %r"%attr)
>             self._lookingup = True
>             return getattr(self, attr.lower())
>         finally:
>             self._lookingup = False
>
>     def __setattr__(self, attr, value):
>         super(C, self).__setattr__(attr.lower(), value)
>
> Matt




More information about the Python-list mailing list