отрицательное предварительное утверждение не работает в python

Задача:
- задано: список имен файлов изображений
- задача: создать новый список с именами файлов, не содержащими слова "thumb" - т.е. ориентироваться только на изображения без миниатюр (с помощью PIL - Python Imaging Library).

Я пытался r".*(?!thumb).*", но это не удалось.

Я нашел решение (здесь, в stackoverflow), чтобы добавить ^ к регулярному выражению и поместить .* в отрицательный прогноз: r"^(?!.*thumb).*", и теперь это работает.

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

Что я понимаю, так это то, что ^ сообщает синтаксическому анализатору, что следующее условие должно соответствовать началу строки. Но разве .* в (неработающем) первом примере также не начинается в начале строки? Я думал, что он начнется с начала строки и будет искать как можно больше символов, прежде чем достигнет «большого пальца». Если это так, он вернет несоответствие.

Может кто-нибудь объяснить, почему r".*(?!thumb).*" не работает, а r"^(?!.*thumb).*" работает?

Спасибо!


person Erik    schedule 14.12.2012    source источник
comment
Ммм - не содержит большого пальца слова - мое ударение... иначе зачем использовать регулярное выражение?   -  person Jon Clements♦    schedule 14.12.2012
comment
Хотя использование регулярных выражений не подходит для этой задачи (может быть, домашнее задание?), последний вопрос остается в силе.   -  person mmgp    schedule 14.12.2012
comment
Привет, Джон, спасибо за быстрый ответ. Я использовал регулярное выражение, потому что я начал анализировать пути в каталоге и имени файла с помощью регулярных выражений. Я новичок в Python, и регулярные выражения — это то, на что я наткнулся, пытаясь справиться с этими задачами. Поскольку я новичок в Python, я, вероятно, не знаю других (более простых?) решений. Не могли бы вы объяснить ударение на слове? «Слово» как 16-битное целое число?   -  person Erik    schedule 14.12.2012
comment
Хорошо, спасибо mmgp, получил комментарий о том, почему это не применяется (задача: создать список ...), мой реальный вопрос: как отсортировать строки, содержащие определенное слово, с помощью регулярных выражений в Python?   -  person Erik    schedule 14.12.2012
comment
Как упоминал @mmgp, мне все же хотелось бы знать, почему первый пример регулярного выражения не работает, а второй работает.   -  person Erik    schedule 14.12.2012
comment
@erik ладно, попытался объяснить   -  person Jon Clements♦    schedule 14.12.2012
comment
@Jon, спасибо, см. мой ответ ниже.   -  person Erik    schedule 14.12.2012
comment
Все еще раздумываю над тем, почему ведущее ^ необходимо в сложном решении этой проблемы с регулярными выражениями, но в настоящее время я играю на Python 2.7+ Regex Tester чтобы узнать.   -  person Erik    schedule 14.12.2012


Ответы (3)


(Блин, Джон побил меня. Ну да ладно, вы все равно можете посмотреть на примеры)

Как говорили другие ребята, регулярное выражение - не лучший инструмент для этой работы. Если вы работаете с путями к файлам, взгляните на os.path.

Что касается фильтрации файлов, которые вам не нужны, вы можете сделать if 'thumb' not in filename: ... после анализа пути (где filename — это str).

И для потомков, вот мои мысли об этих регулярных выражениях. r".*(?!thumb).*" не работает, потому что .* жадный, а упреждающий просмотр имеет очень низкий приоритет. Взгляните на это:

>>> re.search('(.*)((?!thumb))(.*)', '/tmp/somewhere/thumb').groups()
('/tmp/somewhere/thumb', '', '')
>>> re.search('(.*?)((?!thumb))(.*)', '/tmp/somewhere/thumb').groups()
('', '', '/tmp/somewhere/thumb')
>>> re.search('(.*?)((?!thumb))(.*?)', '/tmp/somewhere/thumb').groups()
('', '', '')

Последнее очень странно...

Другое регулярное выражение (r"^(?!.*thumb).*") работает, потому что .* находится внутри предпросмотра, поэтому у вас нет проблем с кражей символов. На самом деле вам даже не нужен ^, в зависимости от того, используете ли вы re.match или re.search:

>>> re.search('((?!.*thumb))(.*)', '/tmp/somewhere/thumb').groups()
('', 'humb')
>>> re.search('^((?!.*thumb))(.*)', '/tmp/somewhere/thumb').groups()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'groups'
>>> re.match('((?!.*thumb))(.*)', '/tmp/somewhere/thumb').groups()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'groups'
person Community    schedule 14.12.2012
comment
@Balthamos - в вашем примере я очень хорошо вижу, что первый (.*) потребляет всю строку, а ? делает ее нежадной (почему пустой? Может быть, потому, что * также допускает 0 совпадений? не совсем идея). Я пытался опустить ^, и это привело к humb.jpg. Получается, что (?!.*thumb) потребляет все, что начинается с thumb, независимо от того, что предшествует ему, а конечный .*перехватывает все, что не начинается с thumb, а именно остальное: humb.jpg. Почему отрицательный просмотр вперед не выдает нет совпадений и есть действительный результат, до сих пор остается для меня загадкой. - person Erik; 14.12.2012
comment
Хорошо, я попробовал re.match вместо re.search, и он вернул нет соответствия. Кажется, re.match делает то же, что и ^ в моем рабочем примере с регулярным выражением. Приятно отметить, что re.search не останавливается совпадающим отрицательным просмотром вперед, если есть другие части регулярного выражения, которые могут быть сопоставлены (.* и т. д.). - person Erik; 14.12.2012
comment
Хотя это не объясняет, почему это так и почему ^ работает так же, как re.match. Но я взломаю это в другой день ;). - person Erik; 14.12.2012
comment
'почему пустой? может быть, потому что * также допускает 0 совпадений? не совсем идея' Да, вы поняли. «Почему отрицательный просмотр вперед не выдает несоответствия и есть верный результат, до сих пор остается для меня загадкой». Это тоже было странно для меня. Мне нужно подумать об этом, чтобы придумать уважительную причину, почему это происходит. re.match должно соответствовать всей строке (по существу '^regex$') re.search может соответствовать любой части строки (по существу ^.*regex.*$) - person ; 15.12.2012
comment
Возможно, совпадение все же есть, потому что именно так запрограммированы утверждения с отрицательным просмотром вперед (nla) — и в этом случае они могут быть запрограммированы не так, как предполагалось. Таким образом, nla просто вырезает несоответствующую часть из re.search, и только в случае re.match это приводит к нету совпадения. Пока мои рассуждения. Когда у меня будет больше времени, я попробую, верно ли это для всех случаев. - person Erik; 15.12.2012

Может кто-нибудь объяснить, почему r".*(?!thumb).*" не работает, а r"^(?!.*thumb).*" работает?

Первый всегда будет совпадать, так как .* будет потреблять всю строку (поэтому за ним не может следовать что-либо, чтобы отрицательный просмотр вперед не сработал). Второй немного запутанный и будет соответствовать от начала строки, наибольшему количеству символов, пока не встретит «большой палец», и если он присутствует, то все совпадение не будет выполнено, поскольку строка начинается с чего-то, за чем следует «большой палец». .

Число два легче записать как:

  • 'thumb' not in string
  • not re.search('thumb', string) (вместо совпадения)

Также, как я уже упоминал в комментариях, ваш вопрос гласит:

имена файлов, не содержащие слово "thumb"

Таким образом, вы можете подумать, следует ли исключить thumbs up или нет.

person Jon Clements♦    schedule 14.12.2012
comment
Спасибо @Джон. Комментарий о том, что .* будет потреблять строку, сделал свое дело. Итак, после того, как .* проанализировано, ничего не остается для оценки, и синтаксический анализатор проходит прямо до конца строки? Если это так, то теперь я понимаю, почему решение ^(?!.*thumb).* действительно работает: оно включает слово «большой палец» в отрицательный просмотр вперед и, таким образом, не позволяет .* пройти мимо него. А без завершающего .* выдает пустой результат/совпадение (но все же результат/совпадение). Две другие нотации, которые вы упомянули (и те, что от @larsks), конечно, намного проще для этой задачи, так что спасибо за это! - person Erik; 14.12.2012
comment
@erik Думаю, ты выразил это лучше, чем я :) - person Jon Clements♦; 14.12.2012
comment
О: Таким образом, вы можете подумать, следует ли исключить thumbs up или нет. Мои имена файлов (изображений) были названы как filename.jpg и filename_thumb.jpg, поэтому простой поиск большого пальца где-нибудь в строке не вызвал бы проблемы в моем случае. Спасибо, что указали! - person Erik; 14.12.2012

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

  • дано: список имен файлов изображений
  • todo: создайте новый список с именами файлов, не содержащими слова "thumb" - т.е. ориентируйтесь только на изображения без миниатюр (с помощью PIL - Python Imaging Library).

Предположим, у вас есть список имен файлов, который выглядит примерно так:

filenames = [ 'file1.jpg', 'file1-thumb.jpg', 'file2.jpg', 'file2-thumb.jpg' ]

Затем вы можете получить список файлов, не содержащих слово thumb, например:

not_thumb_filenames = [ filename for filename in filenames if not 'thumb' in filename ]

Это то, что мы называем пониманием списка. :

not_thumb_filenames = []
for filename in filenames:
  if not 'thumb' in filename:
    not_thumb_filenames.append(filename)

Регулярные выражения на самом деле не нужны для этой простой задачи.

person larsks    schedule 14.12.2012
comment
Спасибо за исчерпывающий ответ! Как уже упоминалось, я не знал о других решениях для этого. Я использовал регулярные выражения в сценарии Perl много лет назад, поэтому они были первым решением goto при решении проблем поиска/сопоставления. - person Erik; 14.12.2012