Комментарии 15
Я так и не понял, для каких задач необходим этот велосипед. os.environ — куда уж проще. subprocess для задач типа ls -l избыточен, но обёртка вокруг него 5 строк займет.
os.environ — куда уж проще.
Одна из идей класса ENV — убрать лишние манипуляции с преобразованиями типов, которые неизбежны при работе с os.environ (типичный пример — проверка переменной DEBUG в окружении). Меньше отвлекающих действий — чище код. Чище код — меньше потенциальных багов. Обе библиотеки, к слову, хорошо покрываются тестами (в версии 1.0.1 почти 100% покрытие выйдет).
subprocess для задач типа ls -l избыточен, но обёртка вокруг него 5 строк займет
А теперь представьте, что вы пишете скрипт автоматизации сборки или что-то в этом роде. Ладно, если «ls» вы замените на os.listdir(), ну а если понадобится «ls -R»? ПредлОжите девопсу разместить нечто вроде такого?
[os.path.join(dp, f) for dp, dn, fn in os.walk(os.path.expanduser("~/files")) for f in fn]
Ну и аналогично насчет проверок, если команда завершилась с ошибкой и прочие прелести нештатных ситуаций.
А если 90%+ скрипта все же будет завязано на использовании Shell, потому что это просто лаконичнее и удобнее, будет ли смысл писать os.listdir() вместо Shell.ls()?
Создавая эти библиотеки, я хотел дать возможность инженерам портировать (и одновременно стабилизировать) свои Bash-скрипты без необходимости глубоко изучать Python. Но есть и обратная сторона: Python-разработчикам, в случае необходимости, также придется меньше возиться с Bash.
А если 90%+ скрипта все же будет завязано на использовании Shell,
То лаконичнее и удобнее использовать чистый shell скрипт, а не Python.
ну а если понадобится «ls -R»
И потом парсить .output
в python? Красиво получится, да. А если вместо предложенного варианта не использовать однострочник с list comprehension, то можно написать читаемый, понятный код.
То лаконичнее и удобнее использовать чистый shell скрипт, а не Python.
Вы вырвали фразу из контекста, причем потеряв по пути важную вторую часть.
Но добавлю по Вашему комментарию: Python может дать много возможностей, которые в Bash достигаются через боль, для манипуляций с данными + их передачей между командами Bash'а.
И потом парсить .output в python? Красиво получится, да. А если вместо предложенного варианта не использовать однострочник с list comprehension, то можно написать читаемый, понятный код.
Да, можно, но в примере с циклом Вам нужно будет прочитать больше строк кода, выполняющих то же самое, что 1-2 строчки с использованием Shell.
Библиотека должна одинаково хорошо и легко работать с любой Bash-командой (ls — это просто пример, не более), вплоть до сложных конструкций и последовательностей команд. И она будет это делать в будущих релизах.
ПредлОжите девопсу разместить..
Предложу писАть на понятном языке ;)
Или, если нужно писать на питоне — выучить какой-нибудь курс «питон за 10 уроков». Писать на питоне, и испытывать сложности с dictionary… Не нужно даже пытаться.
А теперь представьте, что вы пишете скрипт автоматизации сборки или что-то в этом роде.
def RunMeSimple(args,env_=None,cwd="."):
pobj = subprocess.Popen(args, env=env_, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
(stdoutdata, stderrdata)=pobj.communicate()
code = pobj.wait()
out=stdoutdata.splitlines()
err=stderrdata.splitlines()
mix=out.extend(err)
return (out,err,mix,code)
Поставленную задачу (запуск процессов с параметрами и получение результатов) этот код выполняет. Зачем усложнять?
если в переменной окружения лежит сериализованный JSON-подобный объект или даже просто булевая константа
Если раньше это работало на bash — я бы хотел видеть (развидеть не смогу, знаю) ;)
Или, если нужно писать на питоне — выучить какой-нибудь курс «питон за 10 уроков». Писать на питоне, и испытывать сложности с dictionary… Не нужно даже пытаться.
Скорее всего, человек, которому поставлена задача смигрировать скрипты на Python, и так изучит его подобным образом. Но речь не о том, есть ли сложности со словарями или нет — вынуть переменную по ключу сможет любой хелловордщик. Если скрипт не предполагает интерпретацию значений из переменных окружения, то smart-env, вероятно, не даст ощутимой выгоды (кроме, может, как конфигурационный объект). Но с тем же успехом можно назвать Openstack «излишней сложностью», если рассматривать его в качестве площадки для поднятия вордпрессового блога на локалхосте.
Поставленную задачу (запуск процессов с параметрами и получение результатов) этот код выполняет. Зачем усложнять?
Посмотрим на Ваш пример с точки зрения качества кода:
1. Название команды само по себе не является аргументом, который следует класть в args. В Вашем примере объект, вызывающий RunMeSimple(), частично осведомлен о внутренностях вызываемой функции — явный признак того, что код надо переписать.
2. Функция возвращает кортеж из четырех (!) значений. Серьёзно? Получается, вызывающему надо будет ещё и правильно распилить полученный кортеж по переменным и, не дай Бог, не поменять stdout и stderr местами и потом гадать, почему docker ps сыпет текст в поток ошибок.
3. Возможно, я и излишне придрался к конкретному примеру, но, опять-таки, пока проект — это пара скриптов и десяток функций, то можно и subprocess дергать, а вот когда это будет мамонт на овер9000 строк — наверняка вспомните опыт других, но уже станет страшно даже чихнуть не в том месте, чтобы все не рухнуло.
Идея python-shell — это упрощение кода, а не усложнение. Ваш пример преобразовался бы в куда более понятное
cmd = Shell.mycommand(*args, env=env_from_somewhere, cwd='.')
# и где-то ниже, непосредственно там, где это нужно по логике программы
passToSomeFoo(cmd.output)
# и т.п.
причем даже без необходимости создавать под это отдельную функцию (в которой на 95.5% гарантированы копипасты блоков кода из аналогичных «функций-вызывалок»).
Вспомнилась фраза: «Это Си, тут Солнце выкатывают и закатывают вручную». Безусловно, можно продолжать городить кучи комбинаций Popen()+communicate()+<и т.д.> (втайне мечтая переехать на «новенький» 3.5 с 2.7, чтобы суметь в subprocess.run) и кушать кактус. Но зачем?
Если раньше это работало на bash — я бы хотел видеть (развидеть не смогу, знаю) ;)
Про JSON — это не портирование Bash, а новая возможность (хотя попытки ее применения, думаю, не новинка).
Про булевое значение: Вам никогда не приходилось интерпретировать значение переменной окружения на предмет того, True или не True?
# export DEBUG=True
# предположим, если переменная не выставлена, то считаем ее False
DEBUG = bool(os.environ.get('DEBUG'))
# export DEBUG=False
# export DEBUG=0
# и тут номер с невыставлением не проходит...
DEBUG = os.environ.get('DEBUG') not in ("False", "false", 0, None) # красиво?
А теперь, согласно Вашей теории, «усложнённый» вариант:
# скажем, это наш config.py
from smart_env import ENV
ENV.enable_automatic_type_cast()
DEBUG = ENV.DEBUG # и весь парсинг уже сделан за нас - в конфиге только полезная информация!
Надеюсь, я ответил на Ваш вопрос. :)
Вам никогда не приходилось интерпретировать значение переменной окружения на предмет того, True или не True?
Не припомню ;) А зачем хотеть уметь
Следующим шагом захочется уметь yes/no, y/n, да/нет (ya/niht)… КМК, нужно стремиться использовать какой-то один стиль (если у вас python — логично True/False).
Не припомню ;) А зачем хотеть уметь это?
Жизнь — штука непредсказуемая. :)
Вообще, мне часто попадались веб-проекты, где через переменную окружения задавался режим отладки ( и это нормальное явление, имхо). Да и не только из окружения — раз источником переменных выступал key-value Консула. Получались те же Фаберже, только отдельным сервисом.
Следующим шагом захочется уметь yes/no, y/n, да/нет (ya/niht)… КМК, нужно стремиться использовать какой-то один стиль (если у вас python — логично True/False).
В smart-env стиль как раз четко очерчен: строковые константы двух видов — как в Python и как в классическом JSON (как самые потенциально ходовые).
Бэкпорт я видел, но он решает только конкретные несколько вопросов. Использовать у себя, к слову, не стал, т.к. чем меньше third-party в данном случае, тем лучше.
А python-shell позиционируется как куда более универсальная обертка, которая позволит значительно разгрузить скрипты на предмет импорта и использования различных модулей.
ПредлОжите девопсу разместить нечто вроде такого?
os.path
— это очень низкоуровневая штука. Забудьте уже о ней. Уже давно в стандартной библиотеке есть pathlib
и shutil
, которые покрывают почти все операции с файлами.
Что вам нужно сделать? Просто получить все файлы в какой-то поддиректории в домашней директории?
files = (Path.home() / 'files').glob('*')
Если с рекурсивным спуском вглубь:
files = (Path.home() / 'files').rglob('*')
По-моему, очень читабельно и понятно.
Сравните создание директории:
- в
pathlib
:Path('/tmp/new_folder').mkdir(parents=True)
- в
python-shell
:Shell.mkdir('-p', '/tmp/new_folder')
По-моему, pathlib
опять выигрывает в читабельности. А если учесть, что объекты Path
очень легко объединять (например, Path('dir') / 'subdir' / 'file.txt'
), легко извлекать и менять их компоненты (например, Path('dir/a/b/c').relative_to('dir/')
даёт Path('b/c')
) и их можно подавать в стандартные функции для работы с файлами без преобразования, то я вообще не вижу причин не использовать pathlib
.
Для файловых операций есть shutil
, в котором можно найти функции для копирования, перемещения и даже создания архивов.
Можете привести пример, когда python-shell
выигрывает у этих модулей из стандартной библиотеки?
А мне больше нравится подход shellpy.
Он обсуждался на хабре.
Спасибо за ссылку, почитаю.
Да, shellpy делает код очень похожим на pure bash, и в этом одновременно плюс и минус такого подхода. Плюс в том, что код действительно можно переносить практически через Ctrl+C — Ctrl+V. Но в чем тогда резон вообще переносить всё на Python, если скрипты будут выглядеть, как «нечто похожее на Bash, но не Bash»? Я к тому, что читабельности это не добавит, а багов может даже прибавить (на тех операциях, которые в Shell делаются под капотом).
Вообще, библиотека python-shell исповедует всё-таки больше Python-овый стиль, поэтому и была сделана попытка реализовать удобное решение на базе ООП. Вспомните ORM-решения — с ними ведь обычно приятнее и быстрее работать, чем с raw-SQL, который еще и отличается в разных диалектах.
Вообще, параметр '-p'
в Shell.mkdir('-p', '/tmp/new_folder')
не очень-то добавляет читабельности и не способствует отсутствию багов.
Сдруживаем Python и Bash: библиотеки smart-env и python-shell