Как-то раз я уже веселил народ питоновой задачкой про move_month. Было весело и полезно, мне понравилось! Ловите новую задачку.
Дано:
sys.path — список директорий, в которых его можно найтиsys.path показывают на уникальные директории (на практике это может быть не так, но и ладно)Задачка в том, чтобы отрезать от начала имени файла тот префикс, по которому он был найден. То есть, из
sys.path = ['/home/projects', '/home/projects/domination', '/usr/lib/python']
filename = '/home/projects/domination/world/nuclear_winter.py'
должно получится:
world/nuclear_winter.py
Оптимизировать будем по скорости и элегантности. Читаемость, как и в прошлый раз, условие не обязательное (иначе никакого веселья!), но как обычно в питоньем мире — это бонус.
И заодно расскажу практическую пользу. Это часть задачи записи ошибок веб-приложения в лог. Чтобы лог было удобно grep'ать, он однострочный, а следовательно весь traceback в него записывать нельзя (технически-то можно, но получается каша). Однако полезно вместе с текстом exception'а всё же писать имя файла и номер строки, где она произошла. Ну и в целях сокращения упомянутой каши хочется оторвать неинтересные участки путей от имён файлов.
Комментарии: 32
8.06.09 19:46
из
не выйдет
опечатка в "domintation"
простейшее решение
8.06.09 20:05
или вариант покороче
8.06.09 20:09
Как видно, код небезопасный. По-хорошему нужна обертка в try/except или проверка на длину нового списка.
Короче, будем считать, что в path есть необходимый путь.
8.06.09 20:16
Однострочник (перенос для «удобства» чтения).
Это, правда, если считать что в sys.path пути обязательно идут без оконечного «/». Если нет то надо еще поправку вносить на это.
8.06.09 20:26
Ой, вру-вру-вру, грубо и некрасиво ломаюсь если в sys.path лежит '/home/dominatio'. Поправляюсь, заодно убираю ограничение на слэши в конце. Увы, уже более громоздко выходит все
8.06.09 20:47
Алексей, кстати, '/' можно добавлять к
os.path.normpath(p). Букв больше, но кажется понятней.8.06.09 20:56
угу, ту же ошибку допустил
вместо '/' лучше использовать os.path.pathsep
8.06.09 21:11
Сразу к
normpath(p)можно добавлять только при условии что мы никогда не вздумаем иметь какую-нибудь хитрую chroot-среду с'/'вsys.path. Именно для этого там пляска сp.endswith('/')вместо простого захода «в лоб».Если этот случай с путем сразу на корень игнорировать, то
normpath, как я понимаю его поведение, он всегда будет убирать последний слэш и все сведется к чему-то типа8.06.09 21:27
ололо
8.06.09 21:36
filename[len(max([p for p in sys.path if filename.startswith(p)], key=len)):].lstrip(os.path.pathsep)
8.06.09 21:48
Немного оптимизировал:
8.06.09 22:06
import sys
from os.path import sep
В зазипованных файлах он ничего не найдет, конечно, но раз это рабочие модули, то вряд ли они окажутся в zip'ах.
(Запостил не читая комменты, так что простите, если что.)
8.06.09 22:16
А вот с претензией на кроссплатформенность и скорость при многократном вызове при записи ошибок в лог. И вообще - забавы ради. :-)
И пусть никогда не упрется запись ошибок твоих в скорость! О:-)
8.06.09 22:56
Несколько комментариев.
Не знал, что на boolean можно умножать %-). Прикольно!
Только тогда уж
os.path.sep.pathsep— это разделитель целых путей.Отлично! lstrip, кажется, симпатичней слайса по длине.
О! У нас похожая регулярка (Андрей придумал), хотя в деталях решение отличается. Собственно, это, кажется, и самое быстрое решение в сравнении с перебором строк в списке.
8.06.09 23:12
min((
(filename[len(p):] if filename.startswith(p) else filename)
for p in (
(p if p.endswith('/') else p+'/') for p in sys.path
)
), key=len)
8.06.09 23:30
Э-э-э, или я чего-то не понимаю или
lstripнельзя использовать ни в коем случае. Это же совсем не то...8.06.09 23:46
в порядке бреда, строго не пинайте)
но работает вроде быстро
8.06.09 23:53
А, да, не работает lstrip, значит...
Фактически это выражние цикла через рекурсию, так что по скорости не будет сильно отличаться от решений с for. Правда, конкретно это еще и не корректно, потому что не сортирует пути.
9.06.09 00:44
хм, что-то не догоняю зачем сортировать пути.. ведь поиск у нас в силу rsplit идет справа, и по этому будет выбран больший по длине путь
9.06.09 01:12
или я недогоняю условие задачи?
9.06.09 01:23
Не, всё так. Я только не понял, почему ты используешь '%' вместо проверки на startswith?
P.S. Клёвый смайлик на четвёртой строке :-)
P.P.S. Аргумент под max() можно списком не делать, ему генератора достаточно.
9.06.09 01:36
ну очевидно почему % - потому что x.startswith(s) читается не так приятно как s in x.
А я люблю красивые буковки. да и работает это, как не странно быстрее.
9.06.09 02:27
Да, работает быстрее. В 2.5 Питоне сделали оптимизацию 'in' для строк. Только я думал, что в startswith она тоже работает. Оказывается, нет:
И даже если избавиться от лукапа атрибута, всё равно медленней:
9.06.09 03:57
Уверен, что уважаемые господа уже дали самый лучший ответ.
Про лог ошибок. Это одна из вещей, которые мне жутко понравились в Google AppEngine (база не понравилась). У них что-то вроде сислога с удобным просмотром через веб. То есть всё из logging попадает туда, весь stdout и stderr попадает туда, трейсбеки попадают туда отдельно и читабельно. Собираюсь такое прикрутить в проекте на работе и, вообще, считаю, что это самый лучший способ.
9.06.09 09:16
А я, кстати, вчера подумал срелизить наш django_errorlog, который эту задачку и породил.
9.06.09 09:46
К вопросу о трейсбэках ошибок.
Мне тут подумалось (буквально на той неделе применительно к подобной задачке), что в трейсбэк стоило бы вместо имени файла совать имя модуля, то есть имя, по которому он был импортирован. Строчка традиционного трейсбэка выглядит так:
File "/usr/lib/python2.6/json/encoder.py", line 344, in default
Причем пример выше еще довольно прост, потому что там в пути не фигурируют site-packages, и всякие eggs, которые по традиции грешат длиннючими путями. Вместо этого можно наблюдать гораздо более понятное
Module "json.encoder", line 344, in default
И вообще, сущность задачи, надо полагать, не в том, чтобы сократить имя файла, а в том, чтобы в трейсбэке ошибки оно было понятней и читабельнее? :)
Реализуется это довольно просто - при спуске по трейсбэку или подъеме по фреймам смотреть в frame.f_globals на предмет переменной __name__. Если она там есть - это как раз и будет имя импорта модуля, в прострастве имен которого данный фрейм и лежит.
Могу кусок кода в ~ 40 строк приложить в качестве proof of concept :)
9.06.09 10:04
не знаю как будет по скорости, но по читабельности вроде нормально :)
9.06.09 11:58
Угу, последним абзацем поста я как раз про это и говорил.
Спасибо вам за наводку на
__name__в глобальных переменных фрейма! Теперь весь код облагораживания именя файла я похоронил и смотрю туда напрямую. Но для истории вот код, который у нас имя файла обрабатывал:Как видно, для поиска пути используется regexp, который посредством
subсам отрезает себя от имени файла. Сам regexp компилируется один раз и навешивается на атрибут функции для дальнейшего использования.9.06.09 12:44
мне почему-то кажется, что при таком варианте часть решений будет тупить
9.06.09 13:00
обновленное извращение
11.06.09 22:53
У нас в лог пишется полный traceback, иногда с дополнительными строками пояснений.
Простеньким приложением на haskell выдёргиваются все уникальные ошибки (отбрасывая дату-время):
Переписывать на python лениво. :)
11.06.09 23:00
Мы обошлись без кастомных скриптов. Просто пишем два лога: один с однострочными exception'ами, второй с полными traceback'ами. Первый удобно grep'ать, мониторить, статистику снимать. Во второй смотрим, когда надо собственно traceback почитать.