【问题标题】:Designing callbacks with method "overloading" in Python在 Python 中使用“重载”方法设计回调
【发布时间】:2011-05-20 08:23:59
【问题描述】:

我设计了一个验证 API,其中回调用于检查值。回调签名有两种变体:

def check(self, value):
    pass

def check(self, value, domain_object):          
    pass

调用回调实现的示例:

for constraint in constraints:
    constraint.check(value) 
    # or constraint.check(value, domain_object) depending on the implementation

现在我在调用方法之前反射性地计算参数的数量,并根据结果传递一个或两个参数给它。但这样的风格好吗?

会不会更好

  • 始终使用带有三个参数的签名:check(self, value, domain_object)
  • 为第二种情况使用不同的名称,例如 check_with_domain_object

我认为就 oop 而言,始终使用三参数变体是最简洁的方法。你怎么看?

【问题讨论】:

  • 你不能给domain_object分配一些默认值吗?
  • ... 默认值、*args/**kws、唯一方法名称...取决于。把事情简单化。这就是“Python 方式”,对吧? ;-)
  • 也许您可以添加一个如何使用函数的示例,以澄清问题。
  • @Evpok 我的意图是在实现方面完全避免 domain_object 参数,如果不需要的话。
  • @Space_C0wb0y:我添加了一个调用回调的示例。

标签: python oop callback overloading


【解决方案1】:

最惯用的方法是先尝试两个参数,如果失败,尝试一个:

try:
    callback(value_param, domain_object_param)
except TypeError:
    callback(value_param)

【讨论】:

  • 呃。我完全不同意。咳咳,咳咳,还有更多咳咳。 (我想我阅读问题的方式不同:两种方法/功能都将始终存在,这不是对给定方法的检查,而是模拟方法重载的问题.)
  • @pst:函数是回调。这意味着我假设其他一些函数将其中一个作为参数,但不知道是哪一个。所以它需要一些方法来找出天气来使用domain_object参数。以上就是以最小的开销做到这一点。
  • @Space_C0wb0y:这会吞噬在callback() 中引发的任何TypeError,使调试变得更加困难。
  • @lunaryorn:没错。但是,TypeError 通常表示程序员的错误,这应该通过回调的独立测试来解决。
  • @Space_C0wb0y:同意,但你不能依赖测试覆盖率,尤其是如果回调可能来自 3rd 方模块。显式检查签名并不比 try-catch 难多少,但如果回调中存在未被测试捕获的错误,则可以让生活变得更轻松。
【解决方案2】:

我喜欢@Space_C0wb0y 的回答,它类似于 Raymond Hettinger 发送给我的代码,用于解决 pyparsing 中的类似情况(见下文)。对于您的简单情况,请尝试使用此规范器类来包装给定的回调:

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)

您的代码现在可以将回调包装在 _ArityNormalizer 中,并且在回调时,始终使用 2 个参数进行调用。 _ArityNormalizer 将只执行一次试错法“使用 2 个参数调用,如果调用失败,使用 1 个参数调用”逻辑,从那时起将直接转到正确的形式。

在 pyparsing 中,我想支持可能被定义为采用 0、1、2 或 3 个参数的回调,并编写代码,根据回调函数的签名,用几个装饰器之一包装被调用的函数。这样,在运行/回调时,我总是用 3 个参数调用,装饰器负责使用正确数量的参数进行实际调用。

我的代码做了很多脆弱/不可移植/版本敏感的签名内省来做到这一点(听起来像 OP 目前正在做的事情),直到 Raymond Hettinger 给我发了一个很好的 arity-trimming 方法,它基本上做了什么 @ Space_C0wb0y 的回答建议。 RH 的代码使用了一些非常简洁的装饰器,包装了一个非局部变量来记录成功调用的数量,这样你只需要经历一次试错,而不是每次调用回调。您可以在 SourceForge 上的 pyparsing SVN 存储库中看到他的代码,在函数 _trim_arity 中 - 请注意,由于使用了“nonlocal”关键字,他的代码具有 Py2/Py3 变体。

上面的_ArityNormalizer 代码灵感来自 RH 的代码,在我完全理解他的代码的魔力之前。

【讨论】:

    【解决方案3】:

    “Space_C0wb0y”使用try ... except TypError 的想法看起来不错,但我不喜欢这可能会吞下其他异常。 “Paul McGuire”对_ArityNormalizer 的建议与漂亮的界面基本相同。

    最后,我决定让事情尽可能简单和面向对象,并始终使用两个参数,即使在某些情况下第二个参数不会被使用:

    实施方面:

    def check(self, value, domain_object):          
        pass
    

    调用方:

    constraint.check(value, domain_object)
    

    【讨论】:

      猜你喜欢
      • 2010-09-30
      • 2012-06-25
      • 2012-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-15
      • 2016-11-12
      相关资源
      最近更新 更多