Sublime Text 3.1 Build 3170 ViewEventListener Super Class 方法

Sublime Text 3.1 Build 3170 ViewEventListener Super Class methods

我更新到 Sublime Text build 3170 today from the last stable build which I think was 3143. I had been using sublime_plugin.EventListener to do some tasks related to .scpt files, etc. To do that I had created a class with basic functionality for viewing binary or encoded files. To use that class I then would subclass it and subclass sublime_plugin.EventListener 这样 sublime 就会调用适当的方法(on_modified、on_load 等)。
但是,我希望在更新后更改为 sublime_plugin.ViewEventListener(扩展了 ViewEventListener 的 api),因为这将使我能够在我的插件中完成更多的事情。
问题是我的 super class 方法没有被 sublime 调用。下面是一些有望解释我的问题的代码。提前致谢。

#this does work
class test:
  def on_modified(self, view):
    print("mod")

class test2(test, sublime_plugin.EventListener):
  pass

这将在对视图进行更改时调用 on_modified,并且每次都会打印 "mod"。

#this does not work
class test:
  def on_modified(self):
    print("mod")

class test2(test, sublime_plugin.ViewEventListener):
  pass

另一方面,这是行不通的。 "mod" 从不打印。我搜索了 google 并尝试调试。我可以确认正在创建一个 ViewEventListener 实例,并且如果 on_modified 在 test2 中,则打印 "mod"。
任何建议将不胜感激。再次感谢。

这是一个有趣的小问题,因为从表面上看,它应该可以正常工作。但是,如果您在 Sublime 插件系统的内部进行足够多的跟踪,其原因就会变得更加明显。

为了这个答案的目的,我使用了一个与你问题中的非工作示例相匹配的插件,我将其命名为 test_plugin.py:

import sublime
import sublime_plugin

class test:
    def on_modified(self):
        print("mod")

class test2(test, sublime_plugin.ViewEventListener):
    pass

首先,Sublime 使用 EventListenerViewEventListener 的方式在插件方面有不同的内部机制。

EventListener 事件同样适用于任何地方的所有视图,因此当 Sublime 加载插件并找到 EventListener class 时,它会立即创建它的实例然后检查查看实例支持的事件。相关代码在 reload_plugin() 方法中的 sublime_plugin.py 中第 142 行:

if issubclass(t, EventListener):
    obj = t()
    for p in all_callbacks.items():
        if p[0] in dir(obj):
            p[1].append(obj)

all_callbacks是一个字典,其中键是事件的名称,值是数组;因此,这将检查事件侦听器实例的 dir() 是否包含事件,如果是,则将其添加到支持该事件的 classes 列表中。

另一方面,ViewEventListener 仅适用于基于 is_applicableapplies_to_primary_view_only class 方法的某些视图。这意味着它必须存储 class 而不是 实例 ,以便在创建新视图时它可以创建一个该视图特定的实例。

相关代码就在上面的下面,sublime_plugin.py 文件的第 156 行:

if issubclass(t, ViewEventListener):
    view_event_listener_classes.append(t)
    module_view_event_listener_classes.append(t)

现在让我们看看当需要引发事件时会发生什么。在我们的示例中,我们正在查看 on_modified 事件,它由 sublime_plugin.py 中的 on_modified 模块函数处理,位于第 566 行(但所有事件的工作方式相似):

def on_modified(view_id):
    v = sublime.View(view_id)
    for callback in all_callbacks['on_modified']:
        run_callback('on_modified', callback, lambda: callback.on_modified(v))
    run_view_listener_callback(v, 'on_modified')

第一部分是常规的EventListener;它会找到之前找到的所有具有 on_modified 处理程序的对象实例,然后直接对它们调用 on_modified()run_callback() 包装器负责为您的分析计时执行可以在 Tools > Developer > Profile Plugins).

中看到

ViewEventListener 的处理发生在 run_view_listener_callback() 中,它在 sublime_plugin.py 中的第 480 行附近:

def run_view_listener_callback(view, name):
    for vel in event_listeners_for_view(view):
        if name in vel.__class__.__dict__:
            run_callback(name, vel, lambda: vel.__class__.__dict__[name](vel))

如果您已经按照示例中的方式定义了 classes,那么这就是事情开始变得糟糕的部分,因为它专门询问 __dict__ 属性是否包含事件调用,并且只在调用时调用它。

请注意基于上述示例插件的 Sublime 控制台中的以下内容:

>>> from User.test_plugin import test, test2
>>> "on_modified" in test.__dict__
True
>>> "on_modified" in test2.__dict__
False

特别是,test2 不直接包含 on_modified 方法,因此它不在 __dict__ 中。在常规 Python 程序中,如果您尝试调用 on_modified,它会注意到它不在对象的 __dict__ 中,然后开始搜索层次结构,在 [=47= 中找到它] 一切都是肉汁。

EventListener 实例的情况下,会发生这种情况; dir() 函数知道其中一个 superclasses 包含 on_modified 和 returns 它,并且调用它的调用在链上进行,一切都按您预期的那样进行.

由于对 run_view_listener_callback 的调用不是直接尝试调用该方法,因此它不会在直接 __dict__ 查找中找到它,因此什么也不做,因为它认为 class 不处理该事件,即使它处理了。

因此,所有这些文字墙的结果是,对于 ViewEventListener,事件必须直接存在于 class 中,即子 classing ViewEventListener 否则它不起作用。

在您的示例中,一种方法是重构您的代码并直接在 test2 中而不是在 test 中定义这些方法。

另一种方法是 "proxy" 它们通过在您的 subclass 中定义方法来实现,但是让正文服从超级 class 版本。这将适当的方法放在 class 的 __dict__ 中,但仍然让实现出现在另一个 class.

class test2(test, sublime_plugin.ViewEventListener):
    def on_modified(self):
        super().on_modified()

我不是 Python 大师,但这个大师对我来说似乎并不过分 Pythonic(至少对于列出的示例而言)并且可能还有其他方法可以实现同样的事情。