I like @Space_C0wb0y's answer, it is similar to code Raymond Hettinger sent to me to address a similar situation in pyparsing (see below). For your simple case, try using this normalizer class to wrap the given callbacks:
class _ArityNormalizer(object):
def __init__(self, fn):
self.baseFn = fn
self.wrapper = None
def __call__(self, value, domain_object):
if self.wrapper is None:
try:
self.wrapper = self.baseFn
return self.baseFn(value, domain_object)
except TypeError:
self.wrapper = lambda v,d: self.baseFn(v)
return self.baseFn(value)
else:
return self.wrapper(value, domain_object)
Your code can now wrap the callbacks in an _ArityNormalizer, and at callback time, always call with 2 arguments. _ArityNormalizer will do the trial-and-error "call with 2 args and if that fails call with 1 arg" logic only once, and from then on will go directly to the correct form.
In pyparsing, I wanted to support callbacks that may be defined to take 0, 1, 2, or 3 arguments, and wrote code that would wrap the called function with one of several decorators depending on what the callback function's signature was. This way, at run/callback time I'd just always call with 3 arguments, and the decorator took care of making the actual call with the correct number of args.
My code did a lot of fragile/non-portable/version-sensitive signature introspection to do this (sounds like what the OP is currently doing), until Raymond Hettinger sent me a nice arity-trimming method that does essentially what @Space_C0wb0y's answer proposes. RH's code used some very neat decorator wrapping with a nonlocal variable to record the arity of the successful call, so that you only have to go through the trial-and-error once, instead of every time you call the callback. You can see his code in the pyparsing SVN repository on SourceForge, in the function _trim_arity - note that his code has Py2/Py3 variants, due to the use of the "nonlocal" keyword.
The _ArityNormalizer code above was inspired by RH's code, before I fully understood his code's magic.
domain_object?domain_objectparam at all on the implementation side, if it is not needed.