Как-то раз я уже веселил народ питоновой задачкой про 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
из
не выйдет
опечатка в "domintation"
простейшее решение
или вариант покороче
Как видно, код небезопасный. По-хорошему нужна обертка в try/except или проверка на длину нового списка.
Короче, будем считать, что в path есть необходимый путь.
Однострочник (перенос для «удобства» чтения).
Это, правда, если считать что в sys.path пути обязательно идут без оконечного «/». Если нет то надо еще поправку вносить на это.
Ой, вру-вру-вру, грубо и некрасиво ломаюсь если в sys.path лежит '/home/dominatio'. Поправляюсь, заодно убираю ограничение на слэши в конце. Увы, уже более громоздко выходит все
Алексей, кстати, '/' можно добавлять к
os.path.normpath(p)
. Букв больше, но кажется понятней.угу, ту же ошибку допустил
вместо '/' лучше использовать os.path.pathsep
Сразу к
normpath(p)
можно добавлять только при условии что мы никогда не вздумаем иметь какую-нибудь хитрую chroot-среду с'/'
вsys.path
. Именно для этого там пляска сp.endswith('/')
вместо простого захода «в лоб».Если этот случай с путем сразу на корень игнорировать, то
normpath
, как я понимаю его поведение, он всегда будет убирать последний слэш и все сведется к чему-то типаололо
filename[len(max([p for p in sys.path if filename.startswith(p)], key=len)):].lstrip(os.path.pathsep)
Немного оптимизировал:
import sys
from os.path import sep
В зазипованных файлах он ничего не найдет, конечно, но раз это рабочие модули, то вряд ли они окажутся в zip'ах.
(Запостил не читая комменты, так что простите, если что.)
А вот с претензией на кроссплатформенность и скорость при многократном вызове при записи ошибок в лог. И вообще - забавы ради. :-)
И пусть никогда не упрется запись ошибок твоих в скорость! О:-)
Несколько комментариев.
Не знал, что на boolean можно умножать %-). Прикольно!
Только тогда уж
os.path.sep
.pathsep
— это разделитель целых путей.Отлично! lstrip, кажется, симпатичней слайса по длине.
О! У нас похожая регулярка (Андрей придумал), хотя в деталях решение отличается. Собственно, это, кажется, и самое быстрое решение в сравнении с перебором строк в списке.
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)
Э-э-э, или я чего-то не понимаю или
lstrip
нельзя использовать ни в коем случае. Это же совсем не то...в порядке бреда, строго не пинайте)
но работает вроде быстро
А, да, не работает lstrip, значит...
Фактически это выражние цикла через рекурсию, так что по скорости не будет сильно отличаться от решений с for. Правда, конкретно это еще и не корректно, потому что не сортирует пути.
хм, что-то не догоняю зачем сортировать пути.. ведь поиск у нас в силу rsplit идет справа, и по этому будет выбран больший по длине путь
или я недогоняю условие задачи?
Не, всё так. Я только не понял, почему ты используешь '%' вместо проверки на startswith?
P.S. Клёвый смайлик на четвёртой строке :-)
P.P.S. Аргумент под max() можно списком не делать, ему генератора достаточно.
ну очевидно почему % - потому что x.startswith(s) читается не так приятно как s in x.
А я люблю красивые буковки. да и работает это, как не странно быстрее.
Да, работает быстрее. В 2.5 Питоне сделали оптимизацию 'in' для строк. Только я думал, что в startswith она тоже работает. Оказывается, нет:
И даже если избавиться от лукапа атрибута, всё равно медленней:
Уверен, что уважаемые господа уже дали самый лучший ответ.
Про лог ошибок. Это одна из вещей, которые мне жутко понравились в Google AppEngine (база не понравилась). У них что-то вроде сислога с удобным просмотром через веб. То есть всё из logging попадает туда, весь stdout и stderr попадает туда, трейсбеки попадают туда отдельно и читабельно. Собираюсь такое прикрутить в проекте на работе и, вообще, считаю, что это самый лучший способ.
А я, кстати, вчера подумал срелизить наш django_errorlog, который эту задачку и породил.
К вопросу о трейсбэках ошибок.
Мне тут подумалось (буквально на той неделе применительно к подобной задачке), что в трейсбэк стоило бы вместо имени файла совать имя модуля, то есть имя, по которому он был импортирован. Строчка традиционного трейсбэка выглядит так:
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 :)
не знаю как будет по скорости, но по читабельности вроде нормально :)
Угу, последним абзацем поста я как раз про это и говорил.
Спасибо вам за наводку на
__name__
в глобальных переменных фрейма! Теперь весь код облагораживания именя файла я похоронил и смотрю туда напрямую. Но для истории вот код, который у нас имя файла обрабатывал:Как видно, для поиска пути используется regexp, который посредством
sub
сам отрезает себя от имени файла. Сам regexp компилируется один раз и навешивается на атрибут функции для дальнейшего использования.мне почему-то кажется, что при таком варианте часть решений будет тупить
обновленное извращение
У нас в лог пишется полный traceback, иногда с дополнительными строками пояснений.
Простеньким приложением на haskell выдёргиваются все уникальные ошибки (отбрасывая дату-время):
Переписывать на python лениво. :)
Мы обошлись без кастомных скриптов. Просто пишем два лога: один с однострочными exception'ами, второй с полными traceback'ами. Первый удобно grep'ать, мониторить, статистику снимать. Во второй смотрим, когда надо собственно traceback почитать.