【问题标题】:Optimizing `in`优化`in`
【发布时间】:2011-12-28 11:19:10
【问题描述】:

我正在使用这样的构造来测试是否按下了所需的键:

def eventFilter(self, tableView, event):
    if event.type() == QtCore.QEvent.KeyPress:
        key = event.key()
        if event.modifiers() in (QtCore.Qt.NoModifier, QtCore.Qt.KeypadModifier):
            if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
                self.menu.editItem.trigger()
                return True

我知道“过早的优化是万恶之源”,但我认为eventFilter 经常被称为考虑它的优化。

我的担忧:

  1. if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return) 在每次运行时进行双重查找: 1. 在 QtCore 模块中找到 Qt 属性; 2.在Qt模块中找到Key_Enter属性。
  2. if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return) 这会在每次运行时构造元组。元组中的搜索是顺序的 - 最好使用 frozenset?

您如何处理此类情况?不在乎?

【问题讨论】:

  • 您的整个代码可以简化为一个条件检查。
  • 没错,别在意!当然,在你有证据之前,这很重要。 (提示:它没有。)
  • 如果这只是在键盘按下时执行,它的表现是没有意义的。
  • -1: “我知道‘过早的优化是万恶之源’”。好的。到时候,停下来。不要往前走。把时间花在更有价值的事情上。
  • @interjay - 这可能会为 每个 系统 UI 事件(例如鼠标移动或单击)调用,因此 event.type() 的第一次测试可能值得优化, 以快速放弃除按键之外的所有内容。

标签: python optimization python-3.x pyqt pyqt4


【解决方案1】:

您的代码:

def eventFilter(self, tableView, event): 
    if event.type() == QtCore.QEvent.KeyPress: 
        key = event.key() 
        if event.modifiers() in (QtCore.Qt.NoModifier, QtCore.Qt.KeypadModifier): 
            if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): 
                self.menu.editItem.trigger() 
                return True

正如我在对@interjay 的评论中提到的,对于任何类型的 UI 事件,都可能会对该函数进行大量调用,如果您有许多这样的过滤器,它们可能会导致 UI 迟缓。如果您想至少在第一个 if 测试中对其进行优化,请将 QtCore.QEvent.KeyPress 的本地定义移动到默认参数值中:

def eventFilter(self, tableView, event,
    FILTER_EVENT_TYPE=QtCore.QEvent.KeyPress
    ): 
    if event.type() == FILTER_EVENT_TYPE: 
        if event.modifiers() in (QtCore.Qt.NoModifier, QtCore.Qt.KeypadModifier): 
            key = event.key() 
            if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): 
                self.menu.editItem.trigger() 
                return True

(我还将对 event.key() 的函数调用移到 event.modifiers() 测试之后。)

这样的默认参数在导入模块时在函数编译时评估一次,而不是每次调用一次,因此您对QtCore.QEvent.KeyPress的查找将加速。您当然可以将其发挥到极致:

def eventFilter(self, tableView, event,
    FILTER_EVENT_TYPE=QtCore.QEvent.KeyPress
    FILTER_MODIFIERS=(QtCore.Qt.NoModifier, QtCore.Qt.KeypadModifier),
    FILTER_KEYS=(QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return)
    ): 
    if (event.type() == FILTER_EVENT_TYPE and
        event.modifiers() in FILTER_MODIFIERS and 
        event.key() in FILTER_KEYS): 
            self.menu.editItem.trigger() 
            return True

现在您不仅优化了模块对象属性查找,还优化了元组结构,正如@AndrewDalke 所提到的,我对in 的测试表明,它比设置为大约 3 的元组更快或 4 个元素。当条件的任何部分失败时,单个条件仍然会短路,因此如果类型不是按键,您将不会调用 event.modifiers 或 event.key。

编辑:我喜欢 @ekhumoro 对键和修饰符的耦合测试,这是合并到我的代码中的样子:

def eventFilter(self, tableView, event,
    FILTER_EVENT_TYPE=QtCore.QEvent.KeyPress
    FILTER_KEY_MODIFIERS=((QtCore.Qt.Key_Return, QtCore.Qt.NoModifier),
                          (QtCore.Qt.Key_Enter, QtCore.Qt.KeypadModifier),
                          )
    ): 
    if (event.type() == FILTER_EVENT_TYPE and
        (event.key(), event.modifiers()) in FILTER_KEY_MODIFIERS): 
            self.menu.editItem.trigger() 
            return True

【讨论】:

  • 感谢您认真阅读我的问题并回答我的问题以及提示。
【解决方案2】:

我同意 cmets 在这种情况下无所谓的观点。

但是如果你想在这里节省时间,那么在 Python 中消除重复查找成本的规范方法是将对象缓存在局部变量命名空间中:

NoModifier = QtCore.Qt.NoModifier
KeypadModifier = QtCore.Qt.KeypadModifier
if event.modifiers() in (NoModifier, KeypadModifier):
    ...

Python 将在局部变量命名空间、模块命名空间,最后在全局命名空间中查找变量——因此,您可以通过将东西放入局部变量命名空间来获得一些好处。

但是,这在您的情况下没有意义:每个函数调用都会进行一次查找一次。如果您在循环中对相同属性进行很多很多次查找,则上述优化策略适用:

for event in huge_pile_of_accumulated_events:
    if event.modifiers() in (NoModifier, KeypadModifier):
        ...

您可能会保存一些东西 - 请记住首先分析您的代码以实际证明这很重要!对于每个键盘事件运行一次的处理程序,查找时间无关紧要。

【讨论】:

  • 好吧,把它们放在本地命名空间是没有意义的——对每个函数调用进行赋值。至于其他注释 - 我同意
  • 您也可以尝试使用 set() 而不是列表。盈亏平衡点是 2 或 3 个元素。 (__eq__ 的测试比计算 __hash__ 更昂贵。)
  • 我不建议将它们放在本地名称空间中,以便在每个函数调用完成一次分配时有所帮助——这就是为什么我写了关于在循环中处理大量事件的部分。
【解决方案3】:

虽然多个属性查找确实会更慢,因为我们所说的每次查找不到百万分之一秒,但这种差异将被其他更大的因素所淹没(例如简单调用的成本)一个方法)。所以这更像是一种无意义优化而不是过早优化。

但是,如果您真的很担心,只需更改导入语句即可避免很多查找。

所以不要这样做:

from PyQt4 import QtCore

你可以这样做:

from PyQt4.QtCore import QEvent

并避免对模块内的每个 QEvent 引用进行额外的属性查找(当然,以这种方式导入的任何其他类也是如此)。

就个人而言,我也会避免所有in 测试,而是单独测试每种可能性。这节省了每次运行测试时创建元组的成本,并且还利用了短路评估。

所以我会将您的示例代码重写为:

from PyQt4.QtCore import Qt, QEvent

def eventFilter(self, tableView, event):
    if event.type() == QEvent.KeyPress:
        key = event.key()
        modifiers = event.modifiers()
        if ((modifiers == Qt.NoModifier and key == Qt.Key_Return) or 
            (modifiers == Qt.KeypadModifier and key == Qt.Key_Enter)):
            self.menu.editItem.trigger()
            return True

【讨论】:

    【解决方案4】:

    呼应“不要打扰”。但是有几件事您应该注意。

    首先,也是最重要的,函数中的元组字面量在编译函数本身时被构造一次,并存储为常量。所以你没有你要问的情况 - 不断重建元组(以及伴随的查找)。

    其次,将QtCore.QEvent.KeyPress 绑定到模块级全局让您受益(少量)。目前你有一个QtCore 的模块级查找,然后是QEvent 的模块级查找,然后(我认为)是KeyPress 的模块级查找(它可能是类级的,但那是大多数班级的费用相似)。通过执行以下操作:

    QtKeyPress = QtCore.QEvent.KeyPress

    from QtCore.QEvent import KeyPress as QtKeyPress

    您会将其简化为单个模块级别的查找。您可以使用默认参数技巧将其简化为本地查找,但这也有缺点 - 代码丑陋,以及有人在函数调用时覆盖绑定的可能性。

    【讨论】:

    • > 首先,也是最重要的,函数中的元组字面量在函数本身被编译时构造一次,并存储为常量。所以你没有你要问的情况——不断地重建元组(以及伴随的查找)。 QtCore.QEvent.KeyPress 是一个常量,所以它每次都会进行查找。
    • 你说得对——一定是脑子里放了个屁。唯一发生在文字元组中。最好制作一个全局元组并使用它。
    猜你喜欢
    • 1970-01-01
    • 2011-11-24
    • 2011-10-13
    • 1970-01-01
    • 1970-01-01
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多