views:

318

answers:

4

I need to write a class that allows a subclass to set an attribute with the name of a function. That function must then be callable from instances of the class.

For example, I say I need to write a Fruit class where the subclass can pass in a welcome message. The Fruit class must expose an attribute print_callback that can be set.

class Fruit(object):
    print_callback = None

    def __init__(self, *args, **kwargs):
        super(Fruit, self).__init__(*args, **kwargs)
        self.print_callback("Message from Fruit: ")

I need to expose an API that is can be consumed by this code (to be clear, this code cannot change, say it is 3rd party code):

def apple_print(f):
    print "%sI am an Apple!" % f

class Apple(Fruit):
    print_callback = apple_print

If I run:

mac = Apple()

I want to get:

Message from Fruit: I am an Apple!

Instead I get:

TypeError: apple_print() takes exactly 1 argument (2 given)

I think this is because self is passed in as the first argument.

So how do I write the Fruit class? Thanks!

+3  A: 

Python assumes that any functions bound within a class scope are methods. If you'd like to treat them as functions, you have to dig around in their attributes to retrieve the original function object:

def __init__(self, *args, **kwargs):
    super(Fruit, self).__init__(*args, **kwargs)

    # The attribute name was changed in Python 3; pick whichever line matches
    # your Python version.
    callback = self.print_callback.im_func  # Python 2
    callback = self.print_callback.__func__ # Python 3

    callback("Message from Fruit: ")
John Millikin
Or better yet, just add a `self` argument to the function so it can act like a normal method.
Daniel Pryden
According to the question, the callback definition cannot be changed.
John Millikin
BTW, also worth noting is that the `im_func` attribute of methods got renamed to `__func__` in Python 3.x.
Daniel Pryden
+1  A: 

Updated: incorporating abourget's suggestion to use staticmethod:

Try this:

def __init__(self, *args, **kwargs):
    super(Fruit, self).__init__(*args, **kwargs)

    # Wrap function back into a proper static method
    self.print_callback = staticmethod(self.print_callback)

    # And now you can do:
    self.print_callback("Message from Fruit: ")
Daniel Pryden
+2  A: 

You can use directly:

class Apple(Fruit):
    print_callback = staticmethod(apple_print)

or:

class Apple(Fruit):
    print_callback = classmethod(apple_print)

In the first case, you'll get only one parameter (the original). In the second, you'll receive two parameters where the first will be the class on which it was called.

Hope this helps, and is shorter and less complex.

"to be clear, this code cannot change, say it is 3rd party code"
John Millikin
A: 

There's also a bit dirtyer solution with metaclasses:

def apple_print(f):
    print "Apple " + f

class FruitMeta(type):
    def __new__(cls, name, bases, dct):
        func = dct["print_callback"]
        dct["print_callback"]=lambda x,f,func=func: func(f)
        return type.__new__(cls,name,bases,dct)

class Fruit(object):
    __metaclass__ = FruitMeta
    print_callback = None
    def __init__(self):
        super(Fruit,self).__init__()
        self.print_callback("Msg ")

class Apple(Fruit):
    print_callback = apple_print

mac = Apple()here

It manipulates the class before its creation!

pygabriel