views:

90

answers:

2

Hi,

I'm trying to write a Python class that has the ability to do the following:

c = MyClass()
a = c.A("a name for A") # Calls internally c.create("A", "a name for A")
b = c.B("a name for B") # Calls internally c.create("B", "a name for B")

A and B could be anything (well, they're defined in a database, but I don't want to explicitly define them in my code)

A hacky workaround for it would be to do the following:

class MyClass():
    def __init__(self):
        self.createItem = ""

    def create(self, itemType, itemName):
        print "Creating item %s with name %s" % (itemType, itemName)

    def create_wrapper(self, name):
        self.create(self.createItem, name)

    def __getattr__(self, attrName):
        self.createItem = attrName
        return self.create_wrapper

This will work when the user calls something like:

a = c.A("nameA")
b = c.B("nameB")

However, it will fall over in situations where the function pointers are stored without being called:

aFunc = c.A
bFunc = c.B
aFunc("nameA") # Is actually calling c.create("B", "nameA"),
               # as c.B was the last __getattr__() call
bFunc("nameB")

Any suggestions for anything I'm missing here?

Thanks

Edit: I appear to have just figured this one out, but Philipp has a far more elegant solution....

My solution was:

class MyClassCreator():
    def __init__(self, origClass, itemType):
        self.origClass = origClass
        self.itemType = itemType

    def create_wrapper(self, name):
        return self.origClass.create(self.itemType, name)

class MyClass():
    def __init__(self):
        self.createItem = ""

    def create(self, itemType, itemName):
        print "Creating item %s with name %s" % (itemType, itemName)

    def __getattr__(self, attrName):
        return MyClassCreator(self, attrName).create_wrapper

The version that I actually ended up using (as I needed more complexity than a single argument) is: (I don't know if this can be done using a lambda function...)

def __getattr__(self, attrName):
    def find_entity_wrapper(*args, **kwargs):
        return self.find_entity(attrName, *args, **kwargs)

    return find_entity_wrapper
+3  A: 

Have __getattr__ return a local wrapper function:

class MyClass(object):
    def create(self, itemType, itemName):
        print "Creating item %s with name %s" % (itemType, itemName)

    def __getattr__(self, attrName):
        def create_wrapper(name):
            self.create(attrName, name)
        return create_wrapper

There are other ways to create the wrapper function. The simplest one in this case is to use functools.partial:

import functools

class MyClass(object):
    def create(self, itemType, itemName, *args, **kwargs):
        print "Creating item %s with name %s, args %r and kwargs %r" % (itemType, itemName, args, kwargs)

    def __getattr__(self, attrName):
        return functools.partial(self.create, attrName)

c = MyClass()
bFunc = c.B
bFunc("nameB", 1, 2, foo=3)

This will automatically pass all remaining args to the wrapped function.

Philipp
I'm not sure which of yours and Ned's solutions I like best, but they are both far nicer than the one that I came up with (see the edited bottom of my question above)
Hugh
They are not too different—my version is just the spelled-out variant of the lambda version. Lambdas are sometimes frowned upon in Python for being restrictive and harder to read, but semantically they are equivalent to named functions. Usually I use lambdas and other functional-like constructs only for simple expressions that don't modify state.
Philipp
I've put the actual function that I ended up using at the bottom of my question - I actually needed to be able to pass through any number of arguments, which I'm not sure would work with a lambda function...
Hugh
+4  A: 

You can get what you want by simplifying:

class MyClass():

    def create(self, itemType, itemName):
        print "Creating item %s with name %s" % (itemType, itemName)

    def __getattr__(self, attrName):
        return lambda x: self.create(attrName, x)

c = MyClass()
a = c.A("nameA")
b = c.B("nameB")


af = c.A
bf = c.B
af("nameA")
bf("nameB")

prints:

Creating item A with name nameA
Creating item B with name nameB
Creating item A with name nameA
Creating item B with name nameB
Ned Batchelder
Thanks - that looks wonderfully elegant! I've never got my head around lambda before - maybe now should be the time to do that... I'll either go this way or the way that Philipp suggested... Either way works for me!Thanks
Hugh
Actually, the example in my question was a very simplified one - I actually need to be able to pass through a variable (kwargs) number of arguments, and I believe that I can't do that with lambda... Not sure, but it's good to see that Philipp's solution will happily support that.Thanks again.
Hugh