Публикации с меткой «оптимизация»

Блог django на хабрахабре

Django Framework / Как не пересчитывать суммы и средние каждый раз

Представим, что у нас электронная платёжная система, а в ней в базе данных таблица операций. И мы хотим посчитать, например, какого размера средняя операция. Легко, вот запрос, только долго выполняется:

> SELECT avg(amount) FROM transfer;
65.125965782378
generated in 3850 seconds


А теперь представим, что показатель должен быть свежайшим, а записи в таблицу делаются каждую секунду, и за месяц их набираются миллионы. Или другие требования, но суть та же — агрегировать те же данные каждый раз очень затратно. Обычные базы данных не предлагают таких оптимизаций. Как быть?

Ростислав Дзинько

Регулярные выражения в Python: изучение и оптимизация


Writing a regular expression is more than a skill -- it's an art.

Jeffrey Friedl


Что это такое?

Рано или поздно практически каждому программисту в своей жизни приходится сталкиваться с регулярными выражениями.
Термин "Регулярные выражения" является переводом с английского словосочетания "Regular expressions" и есть не совсем точным, а для тех, кто первый раз услышал этот термин, наверное, даже сбивающем с толку (я, например, когда впервые услышал, никак не мог себе вообразить по названию, хотя бы даже примерно, что это, и для чего используется).
Литературный и более осмысленный перевод звучал бы, пожалуй, как "шаблонные выражения". Но название прижилось, а за "шаблонные выражения" вас попросту побьют :).
Отсюда:
Регулярное выражение
это cтрока, задающая шаблон поиска подстрок в тексте.
Регулярные выражения используются для анализа текстов на предмет соответствия находящейся в них текстовой информации некоему шаблону. Например, шаблон задающий слово, содержащее букву "к".


Где применяются регулярные выражения

Регулярные выражения имеют два основных направления применения:
  • анализ и поиск в текстовых массивах
  • проверка данных на соответствие шаблону
Область применения регулярных выражений очень широка. Вот несколько примеров:
  • анализ логов приложений
  • поиск и выборка информации из баз данных, организованных как простые текстовые файлы
  • URL Mapper'ы в веб-фреймворках (например, Django)
  • в приложениях для проверки правильности вводимой информации (например, телефона или адреса электронной почты).


Модуль re

В Python для работы с регулярными выражениями используется модуль re, который входит в стандартную библиотеку Python, начиная с версии Python 1.5. Его предшественник - модуль regex умер, а на Python 1.x сейчас уже, наверное, никто не пишет, так что про него забудьте.


Использование регулярных выражений

Использовать регулярные выражения следует с умом и осторожностью, и только там, где они действительно приносят пользу, а не вред.
Машина регулярных выражений в Python довольно медленная, поэтому может сильно затормозить работу ваших приложений. Поэтому, там, где это возможно, следует пользоваться другими, более подходящими под задачу, средствами. Например, для обработки html использовать регулярные выражения - не очень хорошая идея. Лучше воспользоваться html5lib или BeautifulSoup.
Вторым подводным камнем регулярных выражений есть сложность их прочтения для разработчиков с малым практическим опытом их применения, да и с большим тоже. Разрабатывая регулярное выражение, особенно очень длинное и сложное, следует очень тщательно его тестировать, чтобы не получить неверные результаты в самых неожиданных местах.
Третьей особенностью регулярных выражений является их слабая способность адаптироваться под задачу. То есть при очень небольшом изменении в задании нужное регулярное выражение может вкорне изменить свой вид. Так что при решении задачи с регулярными выражениями следует очень четко определить требования к результатам выполнения задачи.


Правила построения регулярных выражений

Итак, регулярные выражения представляют собой не что иное, как обычные строки. Эти строки могут состоять из:
  • обычных символов (буквы, цифры)
  • управляющих символов: ^ $ * + ? { } [ ] | ( )
Обычные символы значат именно то, что они значат в обычном понимании. Например, регулярное выражение "test" найдет в тексте все слова "test".
Управляющие символы предназначены для создания условий соответствия строки шаблону (ествественно, обычных символов тут недостаточно), и об этом чуть-чуть дальше.
Правило конкатенации: Если A - регулярное выражение и B - регулярное выражение, то AB также регулярное выражение.
Для того, чтобы проще было понимать регулярные выражения при их чтении, советую читать регулярное выражения, преобразуя его про себя в человечески понятный текст. Например, '^test[0-9]*' читать, как "найти текст, начиная с начала строки, который начинается словом test, после которого идет любое количество цифр". Такая интерпретация выражений очень помогает в понимании. Таким способом пользуюсь не только я, но его также рекомендует заслуженный деятель регулярных выражений Джефри Фридл, автор книги "Mastering Regular Expressions".


Средства модуля re для работы с регулярными выражениями

re.match(pattern, string)
Проверяет, соответствует ли начало строки "string" регулярному выражению "pattern". Например, выражению 'test' соответствует строка 'test1', но не соответствует строка '1test'. Возращает объект MatchObject, если строка найдена, или None, если не найдена. Обратите внимание, что также может быть найдена пустая строка, если она соответствует регулярному выражению.
re.search(pattern, string)
Работает аналогично re.match, но проверяет не только с начала строки, а сканирует строку на совпадения полностью. То есть, выражению 'test' будет соответствовать строка '1test', в отличии от предыдущей функции. Зачем две функции? Очевидно, что если вас интересует только начало строки или строка в целом, нужно воспользоваться match, так как скорость его работы будет выше и она не будет делать лишнего сканирования
re.compile(pattern)
"Компилирует" регулярное выражение, заданное в качестве строки в объект для последующей работы. Используется для ускорения работы программы, если одно и то же регулярное выражение используется несколько раз. Например,
compiled_re = re.compile('test')

compiled_re.match('test1')

compiled_re.search('1test')
Соответственно, все поисковые функции дублируются для скомпилированного объекта регулярного выражения, и выступают в качестве методов этого класса, которому стоит передавать единственным параметром анализируемую строку (флаги устанавливаются на этапе компиляции, но об этом немножко позже).
re.findall(pattern, string)
Выполняет поиск всех подстрок в строке, соответствующих регулярному выражению. Возвращает список найденных подстрок, строки не перекрываются.
re.finditer(pattern, string)
Работает так же, как и предыдущая функция, но возвращает итератор, состоящий из объектов MatchingObject.
Кажой из вышеописанных функций также можно передавать флаги, комбинируя их соответствующим образом для того, чтобы подкорректировать выдачу результата. Об этом чуть-чуть позже. В случае компиляции регулярного выражения, флаги передаются на этапе компиляции.


Регулярные выражения на примерах

Итак, перейдем к объяснениям функций механизма регулярных выражений на примерах. Как показывает мой опыт, на примерах регулярные выражения усваиваются гораздо быстрее. Вместе с примерами буду, где это требуется подавать объяснения и "теоретический материал".
Для простоты разбора примеров будем рассматривать только строки, не содержащие символи перевода строки '\n'.
Данные примеры я составил так, чтобы каждый из них демонстрировал некоторую способность механизма регулярных выражений, и это отражается в их названиях. Соответственно в процессе объяснения функций регулярных выражений я буду отталкиваться от задачи, а не от самих функций.


Задача №1: Поиск слова

Дано текст: This is a simple test message for test
Задача: Подсчитать количество слов test в строке.
Решение:

pattern = 'test'
string = 'This is a simple test message for test'
found = re.findall(pattern, string)
len(found) == string.count('test')


Задача №2: Поиск в начале и конце строки

Дано текст: This is a simple test message for test
Задача: Определить, заканчивается ли строка на слово test, и начинается ли на test. Определить является ли строка просто строкой test.
Теория:
Для того, чтобы обозначить конец строки используется символ $, а для обозначения начала строки - ^. Для решения третьей подзадачи пример 1 не подойдет, поскольку поиск ведется по всей строке, поэтому придется использовать оба управляющих символа вместе.
Внимание! Символ ^ обозначает начало строки только тогда, когда он стоит в начале выражения, если нет - он является оператором отрицания, но об этом позже.
Решение:

string = 'This is a simple test message for test'
string2 = 'test'

pattern1 = 'test$'
pattern2 = '^test'
pattern3 = '^test$'

re.search(pattern1, string) is None
False #Строка заканчивается на 'test'

re.match(pattern2, string) is None
True #Строка не начинается на 'test'

re.match(pattern3, string) is None
True #Строка не является строкой 'test'

re.match(pattern3, string2) is None
False #Строка является строкой 'test'


Задача №3: Поиск любого символа

Дано текст: We can get 300 to 540 time faster code if we add about 340 lines of code
Задача: Найти все трехзначные числа в тексте, которые начинаются на цифру 3 и заканчиваются на 0.
Теория:
Для того, чтобы указать, что в искомой строке может находится любой символ (кроме символа новой строки) нужно использовать точку - . . Чтобы учитывался и символ новой строки необходимо установить флаг (но об этом позже).
Решение:

string = 'We can get 300 to 540 time faster code if we add about 340 lines of code'
pattern = '3.0'
found = re.findall(pattern, string)
['300', '340']

Данное регулярное выражение ищет трехзначное число (на самом деле, не трехзначное число, а последовательность из трех символов, даже если они внутри более разрядного числа, первый из которых 3, последний - 0, а между ними - любой символ), но для данной строки и задачи пока достаточно.
Как видим, точка обозначает любой символ. Чтобы заставить регулярное выражение искать строчку '3.0' достаточно поставить перед точкой обратный слеш - '3.\0'.


Задача №4: Поиск по группе символов

Дано текст: If 300 spartans were so brave, so 500 spartans could destroy more than 10k warriors of Darius, but 15k and even 20k.
Задача: Найти все цифры в тексте
Теория:
Для того, чтобы указать механизму искать конкретные символы, используются квадратные скобки - [ и ]. Например, [0-9] - все цифры, [a-z] - все буквы нижнего регистра, [123abc] - любой символ из этих шести символов.
Решение:

pattern = '[0-9]'
string = 'If 300 spartans were so brave, so 500 spartans could destroy more than 10k warriors of Darius, but 15k and even 20k'
set(re.findall(pattern, string))
set(['1', '0', '3', '2', '5'])


Задача №5: Поиск с повторениями

Дано текст: If 300 spartans were so brave, so 500 spartans could destroy more than 10k warriors of Darius, but 15k and even 20k.
Задача: Найти все числа в тексте
Теория:
Механизму регулярных выражений можно указывать, что некоторая последовательность может повторятся. Например, в предыдущей задачи мы искали отдельные цифры, представляющие собой единичные символы. Для того, чтобы искать числа нужно указать, что эти символы могут повторятся. Для этого существуют следующие управляющие символы:
* - предшествующее выражение может повторяться 0 или больше раз (то есть пустую строку также найдем)
+ - предшествующее выражение может повторяться 1 или больше раз (пустую стркоу не найдем)
? - предшествующее выражение может повторяться 0 или 1 раз (пустую строку найдем). Соответственно, если вопросительный отсутствует, выражение должно повториться 1 раз.
Решение:

pattern = '[0-9]+'
string = 'If 300 spartans were so brave, so 500 spartans could destroy more than 10k warriors of Darius, byt 15k and even 20k'
set(re.findall(pattern, string))
set(['300', '10', '15', '500', '20'])


Задача №6: Сокращенная запись последовательностей

Дано тексты:
The temperature can be in range 10-15C next week though it was lesser last week(4-9C). It was -5 some time ago.
The temperature can be in range 10- 15C next week though it was lesser last week(4 - 9C). It was even -5 some time ago.
Задача: Найти все диапазоны чисел в строке
Теория:
Для того, чтобы выделить диапазон нам потребуется указать символ дефиса - '-'. Так как это управляющий символ, нам необходимо его экранировать, то есть использовать обратный слеш - '\'. Для часто используемых групп символов удобнее использовать сокращения для групп. Для цифр - это \d. Другие можно найти в документации.
Решение:

pattern1 = '[\d\-]+'
string1 = 'The temperature can be in range 10-15C next week though it was lesser last week(4-9C).'
re.findall(pattern1, string1)
['10-15', '4-9']

pattern2 = '[\d]+ *- *[\d]+'
string2 = 'The temperature can be in range 10- 15C next week though it was lesser last week(4 - 9C). It was even -5 some time ago'
re.findall(pattern2, string2)
['10- 15', '4 - 9']

Разберем решение второй подзадачи детальнее. Строится регулярное выражение так:
  1. [\d]+ - сначала идет число
  2. |пробел|* - дальше может быть любое количество пробелов, а может и не быть
  3. - - дефис
  4. |пробел|* - дальше может быть любое количество пробелов, а может и не быть
  5. [\d]+ - заканчивается искомая строка числом
Данная задача уже более практическая и приносящая пользу. Но, опять-таки, данное регулярное выражение имеет недостаток. Оно не учитывает отрицательные числа.
Примечание! Сокращенные записи для всех последовательностей можно узнать в документации к модулю re.


Задача № 7: Группировка результатов поиска на примере анализа лога

Дано: строки результата логирования команды ping в Ubuntu Linux.
log=[
'64 bytes from localhost.localdomain (127.0.0.1): icmp_req=1 ttl=64 time=0.033 ms',
'64 bytes from localhost.localdomain (127.0.0.1): icmp_req=2 ttl=64 time=0.034 ms',
'64 bytes from localhost.localdomain (127.0.0.1): icmp_req=3 ttl=64 time=0.031 ms',
'64 bytes from localhost.localdomain (127.0.0.1): icmp_req=4 ttl=64 time=0.031 ms']
Задача: найти пары "номер запроса" -> "время ответа"
Теория
Такой "сырой" лог трудно анализировать. Гораздо лучше то, что мы пытаемся получить в результате выполнения задачи. Естественно сырой лог будет одной строкой, но все же представим, что мы разбили его на отдельные строки. Для того, чтобы получить сгруппированные результаты можно воспользоваться круглыми скобками: ( и ). До этого мы пользовались findall, но MatchingObject имеет параметры group и groups, которые возвращают найденные результаты. groups возвращает кортеж групп результатов, group(number) возвращает результат для группы за номером number.
Если в строке есть несколько групп, которые соответствуют одному и тому же шаблону, не стоит его копировать в выражении несколько раз, достаточно сократить запись к номеру группы. То есть ([abc])([abc]) равнозначно ([abc])(\1). Кто знаком с конфигурацией mod_rewrite, например, в сервере Apache2, точно сталкивался с такой записью, так как там она применяется сплошь и рядом.
Решение:

import pprint
pattern = re.compile('(icmp_req=[\d]+).*(time=[\d\.]+ ms)')
result = []
for line in log:
result.append(pattern.search(line)).groups()
pprint.pprint(result)
[('icmp_req=1', 'time=0.033 ms'),
('icmp_req=2', 'time=0.034 ms'),
('icmp_req=3', 'time=0.031 ms'),
('icmp_req=4', 'time=0.031 ms')]

С такими данными работать уже гораздо проще и приятнее. Итак, разберем выражение:
  1. (icmp_req=[\d]+) - находим число, перед которым идет текст 'icmp_req=' и делаем из него группу символов
  2. .* - дальше идет любой набор символов
  3. (time=[\d\.]+|пробел|ms) - находим число, перед которым идет текст 'time=', и после которого идет пробел и текст 'ms'.


Задача №8: Исключение из поиска

Дано html-код: <p style="margin-left:10px;">text<b class="super-bold">bold text</b>.</p>
Задача: Найти все теги в участке html-кода
Теория:
Как уже упоминалось ранее, символ ^ используется для указанния начала строки, но это только в том случае, если он находится в начале выражения. Если он находится всередине выражения, он действует как оператор исключения из поиска.
Решение:

pattern = '<[^>]+>'
string = '<p style="margin-left:10px;">text<b class="super-bold">bold text</b>.<p>'
re.findall(pattern,string)
['<p style="margin-left:10px;">', '<b class="super-bold">', '</b>', '</p>']


Задача №9: Ограничение выдачи по длине или жадность регулярных выражений

Дано: список изделий, заданных строками в следующем виде
things = ['"Table" "1" "200$"',
'"Stool" "2" "100$"',
'"Mirror" "3" "400$"']
Задача: извлечь из списка параметры изделий
Теория:
Регулярные выражения обладают такой особенностью, как "жадность". Это значит, что в результат поиска попадет как можно более длинное совпадение. Механизм регулярных выражений имеет средство минимизации поисковой выдачи. Для этого следует добавлять после символа повторения восклицательный знак.
Решение:

import pprint
pattern = re.compile('".*?"')
result = []
for line in things:
result.append(pattern.findall(line))
pprint.pprint(result)
[['"Table"', '"1"', '"200$"'],
['"Stool"', '"2"', '"100$"'],
['"Mirror"', '"3"', '"400$"']]

Также данную задачу можно решить способом, описанным в предыдущем примере, применив следующее регулярное выражение: '"[^"]*"'


Задача №10: Корекция выдачи по количеству повторений

Дано текст: 333334 333 123 2334 33345 54443 2195433333332 123333333 44444
Задача: Найти все последовательности цифер 3 в строке, длиной от 2 до 4-х символов.
Теория:
Для того, чтобы указать количество повторений последовательности в регулярных выражениях используются фигурные скобки - { и }. При этот можно задавать как диапазон повторений, так и фиксированное количество. Например, выражение 'a{3}' найдет все последовательности по 3 буквы 'a' подряд, а выражение 'a{3,5}' найдет все последовательности литер 'a' длиной от 3 до 5.
Решение:

pattern = '3{2,4}'
string = '333334 333 123 2334 33345 54443 2195433333332 123333333 44444'
re.findall(pattern, string)
['3333', '333', '33', '333', '3333', '333', '3333', '333']

Не очень-то практическая задачка, но, тем не менее демонстрирует контроль за количеством повторений последовательности символов.


Задача №11: Префиксные и постфиксные проверки

Дано текст: 333334 333 123 2334 33345 54443 2195433333332 123333333 44444
Задача: Найти все числа, в которых встречаются последовательности цифер 3 длиной от 2 до 4-х символов.
Теория:
С тем, что было сказано в предыдущих примерах, вряд ли удастся решить данную задачу, а если и удастся, то получится очень большое, некрасивое и трудночитаемое выражение. Для того, чтобы решить данную задачу регулярные выражения предоставляют постфиксные и префиксные проверки, то есть проверки того, следует ли интересующая нас последовательность символов после некоего шаблона или перед неким шаблоном. Вот несколько вариантов применения
НЕ будет совпадать.
Решение:

pattern = '[\d]*(?<!3)3{2,4}(?!3)[\d]*'
string = '333334 333 123 2334 33345 54443 2195433333332 123333333 44444'
re.findall(pattern, string)
['333', '2334', '33345']

Разберем составленное выражение:
  1. [d]* - идет любое количество цифер или цифер нет.
  2. 3{2,4} - последовательность цифер 3 длиной от 2-х до 4-х символов
  3. [d]* - идет любое количество цифер или цифер нет.


Задача №12: Операция "ИЛИ"

Дано текст: ruby python 456 java 789 j2not clash2win
Задача: Найти все упоминания языков программирования в строке.
Теория:
Для того, чтобы указать возможные последовательности символов в конкретном месте строки, в регулярных выражениях используется операция "ИЛИ", обозначается символом |.
Решение:

pattern = 'ruby|java|python|c#|fortran|c\+\+'
string = 'ruby python 456 java 789 j2not clash2win'
re.findall(pattern, string)
['ruby', 'python', 'java']


О примерах

Данные практические примеры помогут решать большинство задач, в которых применяются регулярные выражения. Многую иформацию по регулярным выражениям, как сокращения последовательностей, я не приводил, так как это справочный материал, не нуждающийся в пояснениях. Эту информацию можно с легкостью получить из официальной документации по модулю re.


Флаги

Я уже упоминал о флагах в начале статьи. Флаги используются для модификации поведения механизма поиска. Передаются третим параметром поисковой функции или вторым параметром при компиляции регулярного выражения. Есть следующие флаги:
  • re.DOTALL - символ "." также учитывает переводы строки "\n", если этот флаг не установлен, то перевод строки не будет воспринят как "любой символ"
  • re.IGNORECASE - ищет строки без учета регистра символов, то есть символ 'f' и 'F' будут восприняты как одинаковые
  • re.LOCALE - корректирует поиск под установленную в системе локаль. От этого зависят значения сокращенных записей последовательностей, как \w, \W, \b, \B, которые содержат литеры алфавита.
  • re.MULTILINE - указывает на то, что данная строка "многострочная", то есть содержит символы перевода строки. Это значит, что символы '^' будут '$' учитывать только конец и начало строки, и не будут срабатывать на каждый перевод строки.
  • re.VERBOSE - включает игнорирование пробелов и переводов строки (кроме как при указании набора символов или если пробел указан с обратным слэшом) при создании регулярных выражений. Это позволяет делать регулярные выражения многострочными и добавлять комментарии после символа '#'.
  • re.UNICODE - делает сокращенные записи символьных последовательностей юникодовыми.


Оптимизация регулярных виражений

Оптимизация регулярных выражений как и любая задача оптимизации программного кода является очень веселой. Ниже я предоставлю некие подсказки, которые можно использовать при оптимизации регулярных выражений.
Внимание! Очевидная вещь - основой регулярных выражений являются сравнения строк. Соответственно, чем меньше сравнений производит машина, тем быстрее выполнится поиск. Назовем это "золотым правилом" регулярным выражений.


1. Вам это нужно?

Итак, сформулирую первую подсказку по оптимизации:
Определитесь, нужны ли вам регулярные выражения для данной задачи. Возможно, получится гораздо быстрее, если вы примените другой способ решения.


2. Операция "ИЛИ"

Эту операцию я не упоминал в примерах. Данная операция позволяет задавать условие соответствия. Задается символом |. Последовательность символов будет соответствовать шаблону если она соответствует или одной или другой части шаблона. Примеры:
pattern1 = 'word1|word2|word3|word4'
pattern2 = '[abc|cde]'
pattern3 = '(VeryLongcase|shortcase)'
Сейчас нас больше всего интересует шаблон pattern3. С точки зрения оптимизации скорости выполнения он записан неправильно. Обработка регулярных выражений в Python ведется слева направо, а условия работают в сокращенной форме, то есть если первое выполняется - второе просто не будет проверятся. Итак, мы вычислили первую подсказку:
При использовании операции | располагать части регулярного выражения слева направо следует в порядке возрастания времени проверки каждого из них.


3. Неопределенные повторения

Рассмотрим повторения. Чем больше повторений - тем больше сравнений. Еще одна проблема машины регулярных выражений Python - рекурсивный бектрекинг. Рекурсивный бектрекинг - это алгоритм определения совпадений. Недостатком его является то, что он попробует как можно больше вариантов поиска перед тем как сдаться, но он прост в реализации, поэтому довольно популярен. Поэтому:
Нужно избегать неопределенных повторений - *, +, лучше пользоваться фиксированными ограничителями: {from, to}.


4. Ограничение области поиска

Ограничение области видимости заключается в том, чтобы как можно более сузить область строки, в которой ведется поиска, то есть заставить поиск провалиться как можно раньше, и не делать лишних проверок. Поэтому, следует пользоваться операциями, которые ограничивают область поиска:
Всегда, где этого возможно, нужно как можно сильнее сузить область поиска. Для этого следует использовать индикаторы начала и конца строки ^, $, а также префиксные и постфиксные ограничители.
Также сузить область поиска поможет предварительная обработка текста. Зачастую текст можно урезать в несколько раз, таким образом искать по гораздо меньшей стоке.


5. Компиляция

Об этом уже говорилось, но для порядка сформулируем в отдельную подсказку:
Если вы используете одно и то же регулярное выражения в программе несколько раз - скомпилируйте его с помощью re.compile и используйте скомпилированный вариант для поиска.


6. Множественные выражения

Иногда бывает так, что более очевидным вариантом решения задачи кажется создания
нескольких, 2-х и больше выражений вместо одного. В этом случае следует помнить, что будет выполняться столько же поисковых проходов по тексту. Поэтому:

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


7. Избегайте вложенных выражений

Изнутри машина регулярных выражений работает следующим образом: разбирает регулярное выражение на части и углубляется при сравнении. Данный пример абсолютно оторван от жизни, просто показывает, что нужно уменьшать количество вложенных циклов.
Пример: Есть строка с ценой товара, в которой нужно произвести поиск. Такое регулярное выражение работает правильно: '\b.11.$'.
Поиск ведется следующим образом:
  1. Поиск ведется до нахождения начала слова. Фиксируется.
  2. Дальше ищем единички.
  3. Ищем знак доллара.
  4. Если знак доллара не найден, возвращаемся на шаг 2 и опять ищем единички.
Даже если нет такого, слова, которое заканчивается на $, поиск будет вестить до потери пульса, пока не будет выполнен перебор всех вариантов. Соответственно время выполнения будет вычисляться как длина строки, в которой ищем, умножить на количество двойных единичек, умножить на количество слов.
Вывод: если убрать \b, который толком ничего не делает, и заменить это все дело на '^\S*11\S$' получим поиск с начала строки по символам, не содержащим пробел.
Следующая подсказка:
Избегайте вложенных циклов в регулярных выражениях.
В связи с этим еще одна подсказка:
Никогда не используйте неопределенно длинные повторения символов в начале строки, так как время выполнения будет расти экспоненциально от длины регулярного выражения.


8. Ищите только то, что вам нужно

Пример с тем ще злощасным \b. Нужно найти все слова в тексте. Можно написать так: '[\b\w]+', но достаточно '[\w]+'.
Ищите только то, что нужно, и удаляйте лишнее из регулярных выражений.


9. Группируйте с умом

Механизм группировки очень медленная часть машины регулярных выражений. Поэтому:
следует использовать их только там, где это действительно помогает и нужно в последующей обработке результатов поиска.
Пример:
"(123|456)" - медленно
"123|456"- быстро


Регулярные выражения в Python 3

Регулярные выражения в Python 3 работают точно так же, как и в 2.х. Единственное отличие в том, что отсутствует флаг re.UNICODE, а вместо него добавлен флаг re.ASCII, чтобы производить ascii-only matching. Ну, Python 3 у нас ведь весь такой юникодовый из себя, так что почему так сделали, думаю, пояснения не требуются.


Заключение

При отладке медленных регулярных выражений сильно помогает профилирование. Так что когда соврешенно неочевидно, что можно сделать, - следует запустить профайлер, и посмотреть все узкие места в коде. Особенно это важно при тестировании регулярных выражений. Старайтесь запускать профилирование на как можно больших строках, которые теоретически могут встретится в приложении. Из-за рекурсивной природы машины регулярных выражений в Python, на длинных строках можно получить самые неожиданные результаты, а оптимизация во многих случаях позволяет ускорить выполнение от нескольких часов до микросекунд.

Ростислав Дзинько

Это Python такой тормоз или я?

Пролог
Ускоряем Python-код
Этот небольшой пост хочу посвятить оптимизации скорости выполнения кода на Python. Зачастую говорят, что Python - большой тормоз. При этом представляют куски кода, мол, вот, - попробуй такое ускорить. Да, действительно, некоторый код нельзя ускорить, потому что... потому что гладиолус. Но в огромном количестве случаев случается так, что тормоза программы вовсе не из-за языка, а... правильно, из-за разработчика. И вся проблема кроется в реализации алгоритма. Хочу показать небольшой пример оптимизации небольшой математической задачки. Итак, начнем. Задачка очень простая, взятая из projecteuler.net

Постановка задачи
Найти самый большой палиндром, который является произведением двух трехзначных чисел.

Решение в лоб
Итак, не вдаваясь в исследование математических свойств палиндромов, выполним решение задачки в лоб. Алгоритм примерно такой:
  1. Цикл по числам от 100 до 999.
  2. Внутренний цикл по числам от 100 до 999.
  3. Если произведение переменных итерации внешнего и внутреннего цикла - палиндром и он больше предыдущего найденного - запоминаем его.
  4. Получаем резульат.
Еще раз напомню, что основная задача здесь - написать код, а потом пытаться его оптимизировать, то есть не придумывать другой более быстрый алгоритм, а оптимизировать уже реализованный.

Итак, сделаем программу (Python 3.1)
from timeit import timeit

def is_palindrome(number):
    return str(number) == str(number)[::-1]

MIN = 100
MAX = 1000

def largest():
    max_number = 0
    for i in range(MIN, MAX):
        for j in range(MIN, MAX):
            if is_palindrome(i*j) and i*j > max_number:
                max_number = i*j
    return max_number

print (timeit('largest()', 'from __main__ import largest', number = 1))
print(largest())
Для приблизительного измерения скорости работы программы воспользуемся модулей timeit. Прогоним несколько раз, и получаем:


Результат906609
Самое быстрое время выполнения: 1.6514 секунд.


Оптимизация № 1: очевидная.
Итак, мы видим здесь одно очевидное улучшение, которое можно выполнить. У нас операция умножения выполняется три раза. Сделаем так, чтобы выполнялась 1 раз (дальше буду приводить только код функции largest, чтобы не засорять текст):
def largest():
    max_number = 0
    for i in range(MIN, MAX):
        for j in range(MIN, MAX):
            product = i * j
            if is_palindrome(product) and product > max_number:
                max_number = product
    return max_number
Результат: 906609
Самое быстрое время выполнения: 1.5932 секунд
Прирост: 3.65%

Немного, но, тем не менее, код немножко ускорился.

Оптимизация №2: это просто интересно
Есть такой момент, что иногда очень часто вызываемые встроенные функциям удобно присваивать псевдонимы в локальном пространстве функции (спасибо semargl89). Таким образом интерпретатор не будет каждый раз при вызове функции искать указатель на нее. В данном случае, такая функция - str. Добавим следующую строчку перед функцией is_palindrom, и изменим соответственно саму функцию is_palindrom:
lstr = str
def is_palindrome(number):
    return lstr(number) == lstr(number)[::-1]
Результат906609
Самое быстрое время выполнения1.5514 секунд
Прирост (от первого варианта)6,44%

Еще немножко ускорили.

Оптимизация №3: веселые условия
Итак, смотрим дальше, что же тут можно еще сделать. Цикли мы никак не изменим. А вот количество вызовов злосчастной is_palindrom можно сократить. А это, по большому счету, сама затратная функция, ведь здесь выполняется преобразование чисел, реверсинг строки, а потом еще и сравнение. Не секрет, что Python обладает сокращенной моделью вычисления условий. То есть, если на некотором этапе условной конструкции уже можно дать ответ, остальную часть конструкции он выполнять не будет. Вот новый код:
def largest():
    max_number = 0
    for i in range(MIN, MAX):
        for j in range(MIN, MAX):
            product = i * j
            if product > max_number and is_palindrome(product) :
                max_number = product
    return max_number
Как видите, все, что мы сделали - поменяли местами операнды оператора and: первым теперь идет условие product > max_number. Таким образом, каждый раз, когда произведение не больше уже найденного - мы не проверяем, является ли оно палиндромом. А результаты на самом то деле поражают:

Результат906609
Самое быстрое время выполнения: 0.2731 секунд
Прирост (от первого варианта)604,69%

Это ж уже почти в семь раз! Что ж, идем дальше.

Оптимизация №4: выходит из предыдущей
Итак, в связи с таким поворотом событий, логически предположить, что чем раньше мы находим большие палиндромы, тем меньше раз вызывается is_palinfrom, что нам только на руку. Теперь есть смысл изменить циклы. Сделаем их не возрастающими, а совсем наоборот. Итак, новый код:
def largest():
    max_number = 0
    for i in range(MAX, MIN, -1):
        for j in range(MAX, MIN, -1):
            product = i * j
            if product > max_number and is_palindrome(product) :
                max_number = product
    return max_number
Результат: 906609
Самое быстрое время выполнения: 0.1925 секунд
Прирост (от первого варианта): 857,87%

И еще одно существенное ускорение. Наш код уже быстрее примерно в восемь с половиной раз!

Оптимизация №5: не надо делать все по 2 раза
Просмотрев цикл, мы заметим, что все умножения выполняются по два раза, то есть, кроме i * j выполняется еще и j * i. Давайте исключать такие нехорошие вещи:
def largest():
    max_number = 0
    for i in range(MAX, MIN, -1):
        for j in range(MAX, i - 1, -1):
            product = i * j
            if product > max_number and is_palindrome(product) :
                max_number = product
    return max_number
Итак, мы сделали внутренний цикл поменьше, при этом даже ничего не поломали.

Результат906609
Самое быстрое время выполнения0.0938 секунд
Прирост (от первого варианта)1760,55%

Ну, думаю, хватит
Итак, проведя несколько минут медитации над кодом, можно добиться ускорения примерно в 20 раз. Не так уж плохо. Мораль сей басни такова: "перед тем как грешить на Python, - тщательно проверяйте свой код".

Блог python на хабрахабре

Язык программирования Python / Сортировка миллиона 32-битных int'ов в 2 мегабайтах памяти на Питоне

Мой перевод статьи Гвидо ван Россума:

Меня тут в шутку спросили: смогу ли я отсортировать миллион 32-битных int'ов в 2 мегабайтах памяти на Питоне. Во время размышления, мне пришло в голову задействовать механизм ввода-вывода с использованием буферной памяти.

Вообще, это именно шуточный вопрос — одни только данные займут 4 мегабайта, при условии бинарного представления! Правда, можно пойти на хитрость — взять файл, содержащий миллион 32-битных int'ов. Как же отсортировать их, используя минимальное количество памяти? Это должна быть какая-то разновидность сортировки слиянием, в которой небольшие куски данных сортируются и записываются во временный файл, после чего происходит слияние временных файлов для получения окончательного результата.

Вот мое решение:

Метки

.net .NET C# .sort 1.2 2009 2010 404 error admin ajax amazon analytics and apache api archlinux asp.net async asynchronous autocomplete bash blender blog blogengine blogs book bootstrap bot bpython buildout byteflow bzr C c plus plus C++ cache cbv Chaco checkio chrome ci ckeditor class based views clojure closure cms cms с удобной админкой code coding style collectd COM comet competition conference ConfigParser contest Context continuous integration CouchDB coverage CppCMS cpyext cpython crud csrf CSS ctypes curl custom model fields cx_freeze cython database db dbm dbqueries debian debug debugging decorator decorators deploy deployment descriptor design dev devconf developers development diveintopython Django django 1.2 django 1.3 django advent django framework django template django trunk django weblog django-admin-tools django-cms django-compressor django-hosts django-piston django-registration django-sphinx django.admin djangoadvent djangocms djangodash doc documentation drupal e-legion eclipse EGit emacs encoding Enthought epoll erlang event exception ExtJS fabric facebook fastcgi finaloption fixtures fonts forms formset fp framework freebsd freeswitch fs2web ftp fun funcparserlib functional gae gamin gandi generic views gettext gevent gil git github gitosis Google Google App Engine google picasa Google Translate google wave Google Web Toolkit grab grablab greenlet gtd gui haskell hg hgshelve highlighter host hosting how-to howto html html5lib Hudson humor i18n icfpc ide idiomatic image-scripting improvements Internet interpreter ipython ironpython izmenimsya.ru jabber java javascript jenkins jetbrains JIT job jquery json jstree jython kde kiev kiyv kyivpy l10n ldap library libs Life Links linux Linux & Unix LLVM logging logs lxml Mac OS X magic mail markdown Matplotlib Mayavi maybe mediavirus meetup memcache Memcached memory messages metaclass middleware migration mikrotik mkd model models mod_python mod_wsgi mongodb monitoring mptt musicmans.ru musicx mvc my-projects mysql netCDF networkx newforms newforms-admin news nginx Nhibernate nix nose NoSQL numpy oop open source OpenID openoffice opster optimization oracle orm os pagination parsing path patterns pdf PDF-принтер PEP PEP8 performance performance optimization perl personality photo php picture-driven computing PIL pinax pingback pip plasma plone plugin plugins postgresql programming progress bar psycopg2 py2exe pybb pybbm pycamp pycharm pycon pycow pycurl pydev pygtk pylons PyNGL pypy pyqt PyQt4 pyrad pyramid PySide Python Python 2.5 python 2.7 python 3 python c api python speed python-mssql python3 pywinauto Qt Qt4 queue rabbitmq radius raw sql re redis redsolution redsolution cms regexp regular expressions release repoze.bfg RequestContext reusable apps robokassa rss ru ruby ruby-on-rails sample satchmo scalability SciPy scraping screencast search selenium self.error seo server setattr settings setuptools shell sikuli sms snippet socket.io software sorting south sphinx spider sql sqlalchemy sqlite ssh startup step-by-step subdomain subversion svn SyntaxHighlighter system tags tdd tddspry teh drama template templates templatetags test testing thinkpad threading threads tips tips and tricks tools tornadio tornado tornado server tricks tutorial tweepy twisted twitter typography uapycon Ubuntu ucsvlog uml Uncategorized unicode unit test unit testing UnitTest Unladen Swallow upload urllib urls utf-8 uwsgi validation vcs versioning video vim virtualenv Visual Studio vkontakte voip wave web web-devel web-services web-разработка webdev webfaction webkit webpy websockets webtest widget widgets Win API windows Wirbel work wrapper wsgi wxPython wxWidgets wysiwyg xapian xml xmonad xmpp xpath yandex youtube zip zomg zope [cdata[cbv]] [cdata[ci]] [cdata[class based views]] [cdata[continuous integration]] [cdata[django framework]] [cdata[django-sphinx]] [cdata[django]] [cdata[nginx]] [cdata[python]] [cdata[virtualenv]] [cdata[программирование]] автоматизация администрирование администрирование django админка алгоритмы архитектура атрибуты базы данных Без рубрики безопасность библиотеки блоге бот веб-разработка видео Визуализация данных вконтакте Все записи гвидо ван россум граббер графика графы декоратор декораторы дескриптор дескрипторы документация заметки игра жизнь идея интересное киев Клиентам книги конференция личное математика метаклассы модели модули монады морфология мысли невозможное новости о облачные вычисления обо мне Обработка данных оптимизация оптимизация кода Основная лента основы парсинг парсинг сайтов перевод песочница Питон поебень поиск правила кодирования программирование Проектирование производительность работа рабочее размышлизмы Разное разработка разработка приложений разработки регулярные выражения сайт событие события ссылки статьи тестирование тесты Тюмень убунтариум фигня философия формы форум Хабрахабр хакинг хостинг шаблоны шаблоны проектирования эксперимент Эксперименты юмор я пиарюсь Яндекс