使用“重载”方法设计回调在Python中

发布于 2024-11-08 18:56:31 字数 608 浏览 4 评论 0原文

我设计了一个验证 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 而言,始终使用三参数变体将是最干净的方法。你怎么认为?

I design a validation API where call-backs are used to check values. There a two variants of callback signatures:

def check(self, value):
    pass

def check(self, value, domain_object):          
    pass

Example for calling the callback implementations:

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

For now I count the number of arguments reflectively before the method is called and depending on the result I pass one ore two parameters to it. But is this good style?

Would it be better to

  • always use the signature with three arguments: check(self, value, domain_object) or
  • use a different name like check_with_domain_object for the second case?

I think in terms of oop it would be the cleanest way to always use the three argument variant. What do you think?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(3

赤濁 2024-11-15 18:56:31

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

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

The most idiomatic way would be to first try with two arguments, and if it fails, try with one:

try:
    callback(value_param, domain_object_param)
except TypeError:
    callback(value_param)
回忆那么伤 2024-11-15 18:56:31

我喜欢 @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给我发送了一个很好的数量修剪方法,该方法基本上完成了@Space_C0wb0y的答案建议的内容。 RH 的代码使用了一些非常简洁的装饰器,用一个非局部变量包装来记录成功调用的数量,这样你只需要经历一次试错,而不是每次调用回调时。您可以在 SourceForge 上的 pyparsing SVN 存储库中的函数 _trim_arity 中查看他的代码 - 请注意,由于使用了“nonlocal”关键字,他的代码具有 Py2/Py3 变体。

上面的_ArityNormalizer代码是受到RH代码的启发,在我完全理解他代码的魔力之前。

I like @Space_C0wb0y's answer, it is similar to code Raymond Hettinger sent to me to address a similar situation in pyparsing (see below). For your simple case, try using this normalizer class to wrap the given callbacks:

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)

Your code can now wrap the callbacks in an _ArityNormalizer, and at callback time, always call with 2 arguments. _ArityNormalizer will do the trial-and-error "call with 2 args and if that fails call with 1 arg" logic only once, and from then on will go directly to the correct form.

In pyparsing, I wanted to support callbacks that may be defined to take 0, 1, 2, or 3 arguments, and wrote code that would wrap the called function with one of several decorators depending on what the callback function's signature was. This way, at run/callback time I'd just always call with 3 arguments, and the decorator took care of making the actual call with the correct number of args.

My code did a lot of fragile/non-portable/version-sensitive signature introspection to do this (sounds like what the OP is currently doing), until Raymond Hettinger sent me a nice arity-trimming method that does essentially what @Space_C0wb0y's answer proposes. RH's code used some very neat decorator wrapping with a nonlocal variable to record the arity of the successful call, so that you only have to go through the trial-and-error once, instead of every time you call the callback. You can see his code in the pyparsing SVN repository on SourceForge, in the function _trim_arity - note that his code has Py2/Py3 variants, due to the use of the "nonlocal" keyword.

The _ArityNormalizer code above was inspired by RH's code, before I fully understood his code's magic.

难理解 2024-11-15 18:56:31

“Space_C0wb0y”使用 try ... except TypError 的想法看起来不错,但我不喜欢这样的事实,因为这可能会吞掉其他异常。 “Paul McGuire”对 _ArityNormalizer 的建议本质上与良好的界面相同。

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

实现方:

def check(self, value, domain_object):          
    pass

调用方:

constraint.check(value, domain_object)

"Space_C0wb0y"'s idea to use try ... except TypError looks nice, but I don't like the fact that this could swallow other exceptions. "Paul McGuire"'s suggestion with the _ArityNormalizer is essentially the same with a nice interface.

In the end I decided to keep things as simple and object oriented as possible and go always with two paramters even if there would be several cases in which the second parameter will be unused:

Implementation side:

def check(self, value, domain_object):          
    pass

Calling side:

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