Оптимизация `in` - PullRequest
       6

Оптимизация `in`

2 голосов
/ 28 декабря 2011

Я использую такие конструкции, чтобы проверить, нажата ли нужная клавиша:

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. Найти атрибут Qt в модуле QtCore;2. Найдите атрибут Key_Enter в модуле Qt.
  2. if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return), который создает кортеж при каждом запуске.Поиск в кортеже последовательный - лучше использовать frozenset?

Как вы справляетесь с такими случаями?Не волнует?

Ответы [ 4 ]

3 голосов
/ 28 декабря 2011

Ваш код:

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, может быть множество вызовов этой функции для любого вида события пользовательского интерфейса, и если у вас есть много таких фильтров, они могут создать медленный пользовательский интерфейс. Если вы хотите оптимизировать это хотя бы до первого теста 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 голосов
/ 28 декабря 2011

Я согласен с комментариями, что в данном случае это не имеет значения.

Но если вы хотите сэкономить здесь время, то канонический способ устранить стоимость повторных поисков в 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):
        ...

Там вы можете что-то сохранить - не забудьте сначала профилировать свой код, чтобына самом деле доказать, что это имеет значение!Для обработчика, который запускается один раз для каждого события клавиатуры, время поиска не имеет значения.

1 голос
/ 29 декабря 2011

Хотя верно, что поиск по нескольким атрибутам будет медленнее, так как мы говорим о менее чем миллионной доле секунды на поиск, такие различия будут затоплены другими гораздо более значительными факторами (такими как стоимость простого вызова метод). Так что это будет скорее случай бессмысленной оптимизации, чем преждевременной оптимизации.

Однако, если вы действительно обеспокоены этим, многих поисков можно избежать, просто изменив операторы импорта.

Так что вместо того, чтобы делать:

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
0 голосов
/ 29 декабря 2011

Эхо "не беспокойся". Но есть пара вещей, о которых вы должны знать.

Во-первых, и самое главное, литералы кортежей в функции создаются один раз , когда сама функция компилируется и сохраняется как константа. Таким образом, у вас нет дела, о котором вы спрашиваете - постоянно восстанавливать кортеж (и сопровождающие поиски).

Во-вторых, привязка QtCore.QEvent.KeyPress к глобальному уровня модуля принесет вам пользу (небольшая сумма). В настоящее время у вас есть поиск на уровне модуля для QtCore, затем поиск на уровне модуля для QEvent, за которым следует (я думаю) поиск на уровне модуля для KeyPress (это может быть уровень класса, но это аналогичная стоимость в большинстве классов). Делая что-то вроде:

QtKeyPress = QtCore.QEvent.KeyPress

или

from QtCore.QEvent import KeyPress as QtKeyPress

Вы сократите это до одного поиска на уровне модуля. Вы можете свести его к локальному поиску, используя хитрость параметров по умолчанию, но это также имеет недостатки - уродливость кода и возможность переопределения привязки во время вызова функции.

...