从 API 实例化类时,我需要提供回调。如何急切地将实例本身绑定到回调?

I need to provide a callback when instantiating a class from an API. How can I bind the instance itself to the callback, eagerly?

提问人:Karl Knechtel 提问时间:8/15/2022 更新时间:8/15/2022 访问量:199

问:

我现在在几个不同的主要第三方库和框架中遇到了这个问题。让我试着把它归结为要点:

  • API 提供了一个类 ,其中构造函数需要一个参数。当某些事件发生时(由于我无法控制的复杂逻辑),API 将调用该函数。Examplecallbackcallback
  • 我有一个函数,它接受一个实例并调用各种方法:modifyExample
    def modify(it):
        it.enabled = True
        it.visible = True
        try:
             it.paint('black')
        except AProblemComesAlong:
             it.whip()
    
  • 我想创建一个 .当发生与 关联的事件时,应通过 修改实例。xExamplexxmodify

因此,我想根据 Python 参数绑定器作为参数绑定到。问题是,它还不存在,因为我仍在调用构造函数:xmodify

>>> from functools import partial
>>> x = Example(callback=partial(modify, x))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

当然,我可以通过允许稍后查找名称来避免:NameErrorlambda

>>> x = Example(callback=lambda: modify(x))

但这是后期绑定,因此如果例如我在循环中并且实例是迭代变量,或者如果稍后由于任何其他原因重新分配,则它无法正常工作。instance

如何完成与自身回调的早期绑定?instance

python 回调 闭包 partial-application

评论


答:

1赞 Karl Knechtel 8/15/2022 #1

通常,您可以尝试以下任何一种方法:

  • 仔细检查 API 是否允许您稍后设置回调(两阶段构造):
    from functools import partial
    instance = Example()
    # Read the documentation and see if Example provides something like:
    instance.callback = partial(modify, instance)
    # or possibly they didn't think of using a decorator for their validation logic:
    instance.set_callback(partial(modify, instance))
    
  • 对示例进行子类化,以便它从自己的方法调用回调,并调整构造参数以将该方法用作包装器: 这里的想法归功于@tdelaney。
    from functools import partial
    
    class ContextProvidingExample(Example):
        def __init__(self, *args, **kwargs):
            try:
                my_callback = kwargs['callback']
                kwargs['callback'] = partial(my_callback, self)
            except KeyError:
                pass
            super().__init__(*args, **kwargs)
    
  • 如果不需要灵活性,则可以将逻辑直接集成到子类中:modify
    class SelfModifyingExample(Example):
        def __init__(self, *args, **kwargs):
            if 'callback' in kwargs.keys():
                raise ValueError('cannot override callback')
            kwargs['callback'] = self._modify
            super().__init__(*args, **kwargs)
        def _modify(self):
            self.enabled = True
            self.visible = True
            try:
                self.paint('black')
            except AProblemComesAlong:
                self.whip()
    
  • 作为最后的手段,在字典中注册实例,并安排回调按名称查找它们:这种方式有点笨拙,但您可能会发现实例的字符串名称在代码的其他地方很有用。
    from functools import partial
    
    hey_you = {} # say my name...
    def modify_by_name(name):
        modify(hey_you[name]) # call modify() maybe?
    # Let's use a simple wrapper to make sure instances get registered.
    def stand_up(name):
        result = Example(callback=partial(modify_by_name, name))
        hey_you[name] = result
        return result
    
    who = what = stand_up('slim shady')