Sublime Text 3.1 Build 3170 ViewEventListener超类方法

时间:2018-05-07 23:47:46

标签: python-3.x sublimetext3 sublime-text-plugin

我今天从最后一个稳定版本(我认为是3143)更新到Sublime Text版本3170。我一直在使用sublime_plugin.EventListener来执行与.scpt文件等相关的任务。为此,我创建了一个具有查看二进制或编码文件的基本功能的类。要使用该类,我会将其子类化并继承sublime_plugin.EventListener,以便sublime将调用适当的方法(on_modified,on_load等)。
但是,我希望在更新后更改为sublime_plugin.ViewEventListener(扩展了ViewEventListener的api),因为这样可以让我在插件中完成更多的工作。
问题是我的超类方法没有被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"永远不会打印。我搜索了谷歌并尝试调试。我可以确认正在创建一个ViewEventListener实例,如果on_modified是在test2中那么" mod"打印。
任何建议将不胜感激。再次感谢。

1 个答案:

答案 0 :(得分:1)

这是一个有趣的小问题,因为从表面上看,这似乎应该可行。如果你在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类时,它会立即创建一个实例,然后检查该实例的事件支持。相关代码位于第142行的sublime_plugin.py方法中reload_plugin()

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()是否包含事件,如果是,则将其添加到支持该事件的类列表中。

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

相关代码正好在上面的sublime_plugin.py文件的第156行:

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

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

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))

如果您已经在示例中定义了类,那么这就是事物开始变形的部分,因为它特别询问__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__中,然后开始搜索层次结构,在test中找到它并且一切都是肉汁。

EventListener实例的情况下,这就是发生的事情; dir()函数知道其中一个超类包含on_modified并返回它,调用它的调用会上升到链中,一切都按预期工作。

由于对run_view_listener_callback的调用没有直接尝试调用该方法,因此它在直接__dict__查找中找不到它,因此它什么也没做,因为它认为该类没有处理该事件,即使它确实如此。

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

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

另一种方法是通过在子类中定义方法来“代理”它们,但让主体遵循超类版本。这将适当的方法放在类的__dict__中,但仍然允许实现出现在另一个类中。

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

我不是一个Python大师,但这个对我来说似乎并不过分Pythonic(至少对于布局的例子而言)并且可能还有其他方法可以达到同样的目的。