I've encountered this problem with a few different major third-party libraries and frameworks now. Let me try to boil it down to the essentials:
Example
, where the constructor expects a callback
parameter. When some event occurs (due to complex logic outside my control), the API will call the callback
function.modify
that accepts an instance of Example
and calls various methods on it:
def modify(it):
it.enabled = True
it.visible = True
try:
it.paint('black')
except AProblemComesAlong:
it.whip()
x
of Example
. When an event occurs that is associated with x
, the x
instance should be modified via modify
.Thus, I would like to bind x
as an argument to modify
, per Python Argument Binders. The problem is, it doesn't exist yet, because I am still calling the constructor:
>>> from functools import partial
>>> x = Example(callback=partial(modify, x))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
Of course, I could avoid the NameError
by allowing the lambda
to look up the name later:
>>> x = Example(callback=lambda: modify(x))
but this is late binding, so it doesn't work properly if e.g. I'm in a loop and instance
is the iteration variable, or if instance
is reassigned later for any other reason.
How can I accomplish early binding of instance
to its own callback?
Generally, you can try any of these approaches:
from functools import partial
instance = Example()
# Read the documentation and see if Example provides something like:
instance.callback = partial(modify, instance)
# or possibly they didn't think of using a decorator for their validation logic:
instance.set_callback(partial(modify, instance))
from functools import partial
class ContextProvidingExample(Example):
def __init__(self, *args, **kwargs):
try:
my_callback = kwargs['callback']
kwargs['callback'] = partial(my_callback, self)
except KeyError:
pass
super().__init__(*args, **kwargs)
Credit @tdelaney for the idea here.modify
logic could be integrated directly into the subclass instead:
class SelfModifyingExample(Example):
def __init__(self, *args, **kwargs):
if 'callback' in kwargs.keys():
raise ValueError('cannot override callback')
kwargs['callback'] = self._modify
super().__init__(*args, **kwargs)
def _modify(self):
self.enabled = True
self.visible = True
try:
self.paint('black')
except AProblemComesAlong:
self.whip()
from functools import partial
hey_you = {} # say my name...
def modify_by_name(name):
modify(hey_you[name]) # call modify() maybe?
# Let's use a simple wrapper to make sure instances get registered.
def stand_up(name):
result = Example(callback=partial(modify_by_name, name))
hey_you[name] = result
return result
who = what = stand_up('slim shady')
This way is a bit clunky, but you may find the string name for instances useful elsewhere in the code.