Skip to content

Commit 23e50ea

Browse files
committed
Support callable objects and functools.partial
In Python versions before 3.4, inspect.getargspec() doesn't support objects that define a __call__() method, or functools.partial objects. Add special handling to allow these to be used as callback functions.
1 parent 5f123a9 commit 23e50ea

File tree

2 files changed

+54
-7
lines changed

2 files changed

+54
-7
lines changed

src/liblo.pyx

+22-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ from libc.stdint cimport int32_t, int64_t
2323
from liblo cimport *
2424

2525
import inspect as _inspect
26+
import functools as _functools
2627
import weakref as _weakref
2728

2829

@@ -257,15 +258,29 @@ cdef int _msg_callback(const_char *path, const_char *types, lo_arg **argv,
257258
src,
258259
cb.user_data)
259260

260-
# call function
261-
if _inspect.getargspec(func)[1] == None:
262-
# determine number of arguments to call the function with
263-
n = len(_inspect.getargspec(func)[0])
261+
# determine the number of arguments to call the function with
262+
if isinstance(func, _functools.partial):
263+
# before Python 3.4, getargspec() did't work for functools.partial,
264+
# so it needs to be handled separately
265+
argspec = _inspect.getargspec(func.func)
266+
nargs = len(argspec[0]) - len(func.args)
267+
if func.keywords is not None:
268+
nargs -= len(func.keywords)
269+
else:
270+
if (hasattr(func, '__call__') and
271+
not (_inspect.ismethod(func) or _inspect.isfunction(func))):
272+
func = func.__call__
273+
274+
argspec = _inspect.getargspec(func)
275+
nargs = len(argspec[0])
276+
264277
if _inspect.ismethod(func):
265-
n -= 1 # self doesn't count
266-
r = cb.func(*func_args[0:n])
278+
nargs -= 1 # self doesn't count
279+
280+
if argspec[1] == None:
281+
r = cb.func(*func_args[0:nargs])
267282
else:
268-
# function has argument list, pass all arguments
283+
# function has variable argument list, pass all arguments
269284
r = cb.func(*func_args)
270285

271286
return r if r != None else 0

test/test_liblo.py

+32
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import re
1616
import time
1717
import sys
18+
import functools
1819
import liblo
1920

2021

@@ -191,6 +192,37 @@ def foo(path, args, *varargs):
191192
self.assertIsInstance(self.cb_varargs[1], liblo.Address)
192193
self.assertEqual(self.cb_varargs[2], 'spam')
193194

195+
def testCallbackCallable(self):
196+
class Foo:
197+
def __init__(self):
198+
self.a = None
199+
def __call__(self, path, args):
200+
self.a = args[0]
201+
foo = Foo()
202+
self.server.add_method('/foo', 'i', foo)
203+
self.server.send(1234, '/foo', 23)
204+
self.assertTrue(self.server.recv())
205+
self.assertEqual(foo.a, 23)
206+
207+
def testCallbackPartial(self):
208+
def foo(partarg, path, args, types, src, data):
209+
self.cb = Arguments(path, args, types, src, data)
210+
self.cb_partarg = partarg
211+
self.server.add_method('/foo', 'i', functools.partial(foo, 'blubb'))
212+
self.server.send(1234, '/foo', 23)
213+
self.assertTrue(self.server.recv())
214+
self.assertEqual(self.cb_partarg, 'blubb')
215+
self.assertEqual(self.cb.path, '/foo')
216+
self.assertEqual(self.cb.args[0], 23)
217+
218+
self.server.add_method('/foo2', 'i', functools.partial(foo, 'bla', data='blubb'))
219+
self.server.send(1234, '/foo2', 42)
220+
self.assertTrue(self.server.recv())
221+
self.assertEqual(self.cb_partarg, 'bla')
222+
self.assertEqual(self.cb.path, '/foo2')
223+
self.assertEqual(self.cb.args[0], 42)
224+
self.assertEqual(self.cb.data, 'blubb')
225+
194226
def testBundleCallbacksFire(self):
195227
def bundle_start_cb(timestamp, user_data):
196228
self.assertIsInstance(timestamp, float)

0 commit comments

Comments
 (0)