访问注入对象的包含对象?

发布于 2024-09-19 10:13:08 字数 2021 浏览 10 评论 0原文

给定一个对象 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

再见回来 2024-09-26 10:13:08

给定一个对象 A,其中包含
可调用对象B,有没有办法
从内部确定“A”
B.调用()?

不是在一般情况下,即,在本文中需要无限的通用性 - 除非 B 以某种方式保留对 A 的引用,Python 肯定不会代表 B 保留它。

对于您的预期用例,情况更糟:即使 B 本身确实保留对 A 的引用(作为方法),它也无济于事对象将转换为它的类),因为您正在践踏整个 B 而没有使用该 setattr ,这相当于赋值。在类 Outer 中没有留下任何痕迹,回到快乐的时代,它有一个具有某些特征的 func 属性:该属性不再存在,被您的 删除了设置属性

这并不是真正的依赖注入(一种需要注入对象合作的设计模式),它是猴子修补,我最讨厌的事情之一,它的破坏性、不可恢复的性质(你目前正在努力解决)是一部分为什么我对此感到恼火。

Given an object A, which contains a
callable object B, is there a way to
determine "A" from inside
B.call()?

Not in the general case, i.e., with the unbounded generality you require in this text -- unless B keeps a reference to A in some way, Python most surely doesn't keep it on B's behalf.

For your intended use case, it's even worse: it wouldn't help even if B itself did keep a reference to A (as a method object would to its class), since you're trampling all over B without recourse with that setattr, which is equivalent to an assignment. There is no trace left in class Outer that, back in happier times, it had a func attribute with certain characteristics: that attribute is no more, obliterated by your setattr.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文