Writing a class that accepts a callback in Python?
I need to write a class that allo开发者_如何学Cws 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!
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: ")
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.
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: ")
I was looking for something more like this when I found this question:
class Something:
def my_callback(self, arg_a):
print arg_a
class SomethingElse:
def __init__(self, callback):
self.callback = callback
something = Something()
something_else = SomethingElse(something.my_callback)
something_else.callback("It works...")
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!
You can use Python 3's __func__
or Python 2 im_func
to get function object and call it directly.
For global functions it will work ok. But if you want to call function for specific class instance,
you need to provide class instance as first argument to callback function.
Here is example:
class Test1:
def __init__(self):
self.name = 'Test1 instance'
def OnPrint(self, value):
assert isinstance(self, Test1), 'Wrong self argument type'
print(f'{self.name}: Test1.OnPrint with value {value}')
t1 = Test1()
f1 = t1.OnPrint.__func__
print('1')
f1(t1, 5)
# Will print:
# Test1 instance: Test1.OnPrint with value 5
print('2')
#f1(1,2)
#AssertionError: Wrong self argument type
#f1(2)
#TypeError: Test1.OnPrint() missing 1 required positional argument: 'value'
Please note that you can omit assert isinstance
if you're confident that call is right one.
精彩评论