Given an object A, which contains a callable object B, is there a way to determine “A” from inside B.__call__()?
This is for use in test injection, where A.B is originally a method.
The code is something like this:
# Class to be tested.
class Outer(object):
def __init__(self, arg):
self.arg = arg
def func(self, *args, **kwargs):
print ('Outer self: %r' % (self,))
# Framework-provided:
class Injected(object):
def __init__(self):
self._replay = False
self._calls = []
def replay(self):
self._replay = True
self._calls.reverse()
def __call__(self, *args, **kwargs):
if not self._replay:
expected = (args[0], args[1:], kwargs)
self._calls.append(expected)
else:
expected_s, expected_a, expected_k = self._calls.pop()
ok = True
# verify args, similar for expected_k == kwargs
ok = reduce(lambda x, comp: x and comp[0](comp[1]),
zip(expected_a, args),
ok)
# what to do for expected_s == self?
# ok = ok and expected_s(this) ### <= need "this". ###
if not ok:
raise Exception('Expectations not matched.')
# Inject:
setattr(Outer, 'func', Injected())
# Set expectations:
# - One object, initialised with "3", must pass "here" as 2nd arg.
# - One object, initialised with "4", must pass "whatever" as 1st arg.
Outer.func(lambda x: x.arg == 3, lambda x: True, lambda x: x=='here')
Outer.func(lambda x: x.arg == 4, lambda x: x=='whatever', lambda x: True)
Outer.func.replay()
# Invoke other code, which ends up doing:
o = Outer(3)
p = Outer(5)
o.func('something', 'here')
p.func('whatever', 'next') # <- This should fail, 5 != 4
The question is: Is there a way (black magic is fine) within Injected.__call__() to access what “self” would have been in non-overwritten Outer.func(), to use as “this” (line marked with “###”)?
Naturally, the code is a bit more complex (the calls can be configured to be in arbitrary order, return values can be set, etc.), but this is the minimal example I could come up with that demonstrates both the problem and most of the constraints.
I cannot inject a function with a default argument instead of Outer.func – that breaks recording (if a function were injected, it’d be an unbound method and require an instance of “Outer” as its first argument, rather than a comparator/validator).
Theoretically, I could mock out “Outer” completely for its caller. However, setting up the expectations there would probably be more code, and not reusable elsewhere – any similar case/project would also have to reimplement “Outer” (or its equivalent) as a mock.
Not in the general case, i.e., with the unbounded generality you require in this text — unless
Bkeeps a reference toAin some way, Python most surely doesn’t keep it onB‘s behalf.For your intended use case, it’s even worse: it wouldn’t help even if
Bitself did keep a reference toA(as a method object would to its class), since you’re trampling all overBwithout recourse with thatsetattr, which is equivalent to an assignment. There is no trace left in classOuterthat, back in happier times, it had afuncattribute with certain characteristics: that attribute is no more, obliterated by yoursetattr.This isn’t really dependency injection (a design pattern requiring cooperation from the injected-into object), it’s monkey patching, one of my pet peeves, and its destructive, non-recoverable nature (that you’re currently struggling with) is part of why I peeve about it.