访问注入对象的包含对象?
给定一个对象 A,其中包含一个可调用对象 B,有没有办法从 B.__call__()
内部确定“A”?
这是用于测试注入的,其中AB本来是一种方法。
代码是这样的:
# 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
问题是:在 Injected.__call__()
中是否有一种方法(黑魔法很好)来访问“self”本来在未覆盖的 Outer.func()
中,用作“this”(标有“###”的行)?
当然,代码有点复杂(可以将调用配置为任意顺序,可以设置返回值等),但这是我能想到的最小示例,它演示了问题和大部分内容的限制。
我无法注入带有默认参数的函数,而不是 Outer.func
- 这会中断记录(如果注入了函数,它将是一个未绑定的方法,并且需要“Outer”实例作为其第一个参数,而不是比较器/验证器)。
从理论上讲,我可以为其调用者完全模拟“Outer”。然而,设置期望可能会有更多代码,并且在其他地方不可重用 - 任何类似的案例/项目也必须重新实现“外部”(或其等效项)作为模拟。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不是在一般情况下,即,在本文中需要无限的通用性 - 除非
B
以某种方式保留对A
的引用,Python 肯定不会代表B
保留它。对于您的预期用例,情况更糟:即使
B
本身确实保留对A
的引用(作为方法),它也无济于事对象将转换为它的类),因为您正在践踏整个B
而没有使用该setattr
,这相当于赋值。在类Outer
中没有留下任何痕迹,回到快乐的时代,它有一个具有某些特征的func
属性:该属性不再存在,被您的删除了设置属性
。这并不是真正的依赖注入(一种需要注入对象合作的设计模式),它是猴子修补,我最讨厌的事情之一,它的破坏性、不可恢复的性质(你目前正在努力解决)是一部分为什么我对此感到恼火。
Not in the general case, i.e., with the unbounded generality you require in this text -- unless
B
keeps a reference toA
in 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
B
itself did keep a reference toA
(as a method object would to its class), since you're trampling all overB
without recourse with thatsetattr
, which is equivalent to an assignment. There is no trace left in classOuter
that, back in happier times, it had afunc
attribute 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.
也许您想要Python 描述符协议?
Perhaps you want the Python descriptor protocol?