【问题标题】:Python 3 bound methods subscriptionPython 3 绑定方法订阅
【发布时间】:2013-01-15 01:47:49
【问题描述】:

一开始,我知道 Python 3 中不存在绑定的方法属性(根据本主题:Why does setattr fail on a bound method

我正在尝试编写一个伪“反应式”Python 框架。也许我错过了一些东西,也许,我正在尝试做的事情在某种程度上是可行的。让我们看一下代码:

from collections import defaultdict

class Event:
    def __init__(self):
        self.funcs = []

    def bind(self, func):
        self.funcs.append(func)

    def __call__(self, *args, **kwargs):
        for func in self.funcs:
            func(*args, **kwargs)


def bindable(func):
    events = defaultdict(Event)
    def wrapper(self, *args, **kwargs):
        func(self, *args, **kwargs)
        # I'm doing it this way, because we need event PER class instance
        events[self]()

    def bind(func):
        # Is it possible to somehow implement this method "in proper way"?
        # to capture "self" somehow - it has to be implemented in other way than now,
        # because now it is simple function not connected to an instance.
        print ('TODO')

    wrapper.bind = bind

    return wrapper

class X:
    # this method should be bindable - you should be able to attach callback to it
    @bindable
    def test(self):
        print('test')

# sample usage:

def f():
    print('calling f')

a = X()
b = X()

# binding callback
a.test.bind(f)

a.test() # should call f
b.test() # should NOT call f

当然,所有的类,比如Event,都在这个例子中被简化了。有没有办法修复这个代码工作?我只想能够使用bindable 装饰器使方法(不是函数!)可绑定,并能够稍后将其“绑定”到回调 - 这样,如果有人调用该方法,回调将被自动调用。

Python 3 中有什么方法可以做到吗?

【问题讨论】:

    标签: python events methods python-3.x callback


    【解决方案1】:

    说实话,我没有回答你的问题,只是另一个问题是返回:

    猴子修补您的实例不会产生您想要的行为:

    #! /usr/bin/python3.2
    
    import types
    
    class X:
        def __init__ (self, name): self.name = name
        def test (self): print (self.name, 'test')
    
    def f (self): print (self.name, '!!!')
    
    a = X ('A')
    b = X ('B')
    
    b.test = types.MethodType (f, b) #"binding"
    
    a.test ()
    b.test ()
    

    【讨论】:

    • 你的回答真的很有趣——我不知道types.MethodType 是“手动方法绑定”。无论如何,我看不出它对我的问题有多大帮助:(
    • 对不起,我误解了你的问题。是不是在实例化之后将可调用对象“绑定”到一个实例并且仍然接收到调用实例的self
    • 不完全-我希望能够以这种方式将任何方法/函数绑定到回调(以不同的含义),执行此方法/函数将执行回调。这有点复杂,但真的很有用 - 我找到了解决方案 - 请参阅我的答案。但是,如果你知道一个比我更漂亮或更简单的解决方案,我很乐意看到它:)
    【解决方案2】:

    哦,是的! :D 我找到了答案 - 有点疯狂,但工作很快。如果有人有评论或更好的解决方案,我会非常有兴趣看到它。以下代码适用于方法和函数:

    # ----- test classes -----    
    class Event:
        def __init__(self):
            self.funcs = []
    
        def bind(self, func):
            self.funcs.append(func)
    
        def __call__(self, *args, **kwargs):
            message = type('EventMessage', (), kwargs)
            for func in self.funcs:
                func(message)
    
    # ----- implementation -----
    
    class BindFunction:
        def __init__(self, func):
            self.func = func
            self.event = Event()
    
        def __call__(self, *args, **kwargs):
            out = self.func(*args, **kwargs)
            self.event(source=None)
            return out
    
        def bind(self, func):
            self.event.bind(func)
    
    class BindMethod(BindFunction):
        def __init__(self, instance, func):
            super().__init__(func)
            self.instance = instance
    
        def __call__(self, *args, **kwargs):
            out = self.func(self.instance, *args, **kwargs)
            self.event(source=self.instance)
            return out
    
    class Descriptor(BindFunction):
        methods = {}
    
        def __get__(self, instance, owner):
            if not instance in Descriptor.methods:
                Descriptor.methods[instance] = BindMethod(instance, self.func)
            return Descriptor.methods[instance]
    
    def bindable(func):
        return Descriptor(func)
    
    # ----- usage -----
    class list:
        def __init__(self, seq=()):
            self.__list = [el for el in seq]
    
        @bindable
        def append(self, p_object):
            self.__list.append(p_object)
    
        def __str__(self):
            return str(self.__list)
    
    @bindable
    def x():
        print('calling x')
    
    # ----- tests -----
    
    def f (event):
        print('calling f')
        print('source type: %s' % type(event.source))
    
    def g (event):
        print('calling g')
        print('source type: %s' % type(event.source))
    
    a = list()
    b = list()
    
    a.append.bind(f)
    b.append.bind(g)
    
    a.append(5)
    print(a)
    
    b.append(6)
    print(b)
    
    print('----')
    
    x.bind(f)
    x()
    

    和输出:

    calling f
    source type: <class '__main__.list'>
    [5]
    calling g
    source type: <class '__main__.list'>
    [6]
    ----
    calling x
    calling f
    source type: <class 'NoneType'>
    

    诀窍是使用 Python 的描述符来存储当前实例指针。

    因此,我们能够将回调绑定到任何 python 函数。执行开销并不太大 - empty 函数的执行速度比没有这个装饰器时慢 5 - 6 倍。这种开销是由所需的函数链由事件处理引起的。

    当使用“正确的”事件实现(使用弱引用)时,例如:Signal slot implementation,我们得到的开销是基本函数执行的 20 到 25 倍,这仍然很好。

    编辑: 根据 Hyperboreus 的问题,我更新了代码,以便能够从回调方法中读取调用回调的源对象。它们现在可以通过event.source 变量访问。

    【讨论】:

    • 当你需要访问时你会怎么做? b 来自g,即“绑定”方法中的实例?
    • g 不是 Python 含义中的 bound 方法 - 也许名称有点令人困惑。 g 是对b 的回调,所以每当b 被调用时,g 也会被调用。这旨在在编程 gui 或其他任何东西时可用 - 例如,如果用户向列表添加某些内容,则 gui 应该更改 - 我们可以使用我们的回调方法“订阅”(也许这是更好的词)list.append。经过更多思考,您提出的建议可能有用,我现在正在考虑。
    • 完成。我重新设计了事件流,所以现在您可以访问“从绑定方法中的实例?”带有event.source 变量。当然,这是以最简单的方式完成的,但是这个想法已经显示出来了:)
    • 这种方法的问题在于它使实例作为 Descript.methods 中的键保持活动状态。您应该使用 Wea​​kKeyDictionary,或者将绑定存储在实例的 dict 中(例如,在“$bindings”下)。
    • 你说得对——在我的“最终”实现中,我使用的是WeakKeyDictionary,但这个答案更像是一个“指导方针”。事件有完全相同的问题——它们应该用WeakKeyDictionary 定义方法,WeakSets 定义函数,但分析这段代码不会那么简单:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 2017-08-09
    • 2021-06-14
    相关资源
    最近更新 更多