Python + bash
Если вам часто требуется запускать shell команды из Python-кода, какой способ вы используете?
Самый низкоуровневый это функция
Советую обратить своё внимание на очень крутую библиотеку sh.
Что она умеет?
🔸 удобный синтаксис вызова команд как функций
🔸 удобный вызов команд от sudo
А вот вариант с вводом пароля через код.
Специально для Windows💀 юзеров
#libs #linux
Если вам часто требуется запускать shell команды из Python-кода, какой способ вы используете?
Самый низкоуровневый это функция
os.system(), либо os.popen(). Рекомендованный способ это subprocess.call(). Но это всё еще достаточно неудобно.Советую обратить своё внимание на очень крутую библиотеку sh.
Что она умеет?
🔸 удобный синтаксис вызова команд как функций
# os🔸 простое создание функции-алиаса для длинной команды
import os
os.system("tar cvf demo.tar ~/")
# subprocess
import subprocess
subprocess.call(['tar', 'cvf', 'demo.tar', '~/'])
# sh
import sh
sh.tar('cvf', 'demo.tar', "~/")
fn = sh.lsof.bake('-i', '-P', '-n')
output = sh.grep(fn(), 'LISTEN')
в этом примере также задействован пайпинг🔸 удобный вызов команд от sudo
with sh.contrib.sudo:Такой запрос спросит пароль. Чтобы это работало нужно соответствующим способом настроить юзера.
print(ls("/root"))
А вот вариант с вводом пароля через код.
password = "secret"Это не все фишки. Больше интересных примеров смотрите в документации.
sudo = sh.sudo.bake("-S", _in=password+"\n")
print(sudo.ls("/root"))
Специально для Windows💀 юзеров
#libs #linux
GitHub
GitHub - amoffat/sh: Python process launching
Python process launching. Contribute to amoffat/sh development by creating an account on GitHub.
Делал оптимизацию своей библиотеки кеширования ответов от API. Нужно было выяснить, какие функции заставляют обращаться в API минуя кеш. Мне нужно было видеть полный стек вызова функции запроса в API.
Конечно же первое что приходит в голову это traceback.print_stack(). Но мне не нравится его многословное форматирование. Поэтому я решил сделать свой аналог.
Делается это совсем не сложно, достаточно посмотреть как работает функция print_stack() и сделать аналогично. Все дополнительные функции уже имеются в модуле traceback.
Первым делом достаём фрейм
🔸 Фильтр по имени файла.
Удобно при работе через дебагер, например, в Pycharm. Пропускаются ненужные вызовы.
🔸 Выводить передаваемые аргументы.
Придется сделать свой extract_stack.
🔸 Добавить цвета
Пример реализации здесь. Просто вызовите эту функцию в том месте где требуется увидеть цепочку вызовов.
PS. Да, я знаю на сколько это странный способ дебага. Но такой инструмент потребовался под конкретную ситуацию и он справился хорошо.
PPS:
А теперь суровый хардкод! Всё в одну строку плюс некоторые дополнения в форматировании! (не повторяйте это дома!)
Конечно же первое что приходит в голову это traceback.print_stack(). Но мне не нравится его многословное форматирование. Поэтому я решил сделать свой аналог.
Делается это совсем не сложно, достаточно посмотреть как работает функция print_stack() и сделать аналогично. Все дополнительные функции уже имеются в модуле traceback.
Первым делом достаём фрейм
frame = sys._getframe().f_backТеперь преобразуем его в стек вызовов
stack = traceback.extract_stack(frame)
Теперь у нас есть список инстансов класса FrameSummary в порядке их вызова. Остаётся сделать выводfor item in stack:Получается что-то вроде такого
print(f"{os.path.basename(item.filename)}[{item.lineno}] -> {item.name}")
__main__.py[123] -> <module>Как можно улучшить?
module1.py[456] -> func1
module2.py[789] -> func2
🔸 Фильтр по имени файла.
Удобно при работе через дебагер, например, в Pycharm. Пропускаются ненужные вызовы.
🔸 Выводить передаваемые аргументы.
Придется сделать свой extract_stack.
🔸 Добавить цвета
Пример реализации здесь. Просто вызовите эту функцию в том месте где требуется увидеть цепочку вызовов.
PS. Да, я знаю на сколько это странный способ дебага. Но такой инструмент потребовался под конкретную ситуацию и он справился хорошо.
PPS:
А теперь суровый хардкод! Всё в одну строку плюс некоторые дополнения в форматировании! (не повторяйте это дома!)
print_stack_simple = lambda: print(*[f"{' ' * i}{os.path.basename(item.filename)}[{item.lineno}] -> {item.name}{'()' if not '>' in item.name else ''}" for i, item in enumerate(traceback.extract_stack(sys._getframe().f_back)[:-1])], sep='\n')
#tricksGist
simple_print_stack.py
GitHub Gist: instantly share code, notes, and snippets.
Как получить список месяцев или дней недели на русском языке? Можно "нахардкодить" список прямо в своём коде😖. Но лучше воспользоваться стандартным модулем calendar установив нужную локализацию.
А вот так можно распечатать календарь на год на русском прямо в терминал:
>>> import calendar
>>> import locale
>>> locale.setlocale(category =locale.LC_ALL, locale="Russian")
>>> print(list(calendar.month_name)[1:])
['Январь', 'Февраль', 'Март', ...
>>> print(list(calendar.day_name))
['понедельник', 'вторник', 'среда', ...
>>> print(list(calendar.day_abbr))
['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']
Модуль datetime тоже будет с переводом>>> import datetime
>>> datetime.date.today().strftime('%B %Y года, %A')
'Ноябрь 2020 года, суббота'
(кстати, именно так получается список month_name)А вот так можно распечатать календарь на год на русском прямо в терминал:
print(calendar.LocaleTextCalendar(locale="Russian_Russia").formatyear(2021))
#libs #tricks В стандартных библиотеках Python есть средства для текстового процессинга. Например difflib, fnmatch или тотже re. Но в реальной работе они оказываются "многословны" при при поиске нечётких совпадений строк.
Например, если вы пишете голосовой помощник и вам требуется определить произнесённые ключевые слова. Или в вашем телеграм-боте пользователь вводит определённые слова и возможны ошибки или сокращения.
Вместо того чтобы писать большой список вариантов каждого слова следует применять нечёткое совпадение в тексте.
Для этого хорошо подойдёт библиотека fuzzywuzzy. Она может посчитать на сколько один текст похож на другой или найти из списка строк самое похожее на заданный текст.
Пример поиска имени месяца по введённому юзером тексту. Список месяцев берём из примера прошлого поста.
#libs
Например, если вы пишете голосовой помощник и вам требуется определить произнесённые ключевые слова. Или в вашем телеграм-боте пользователь вводит определённые слова и возможны ошибки или сокращения.
Вместо того чтобы писать большой список вариантов каждого слова следует применять нечёткое совпадение в тексте.
Для этого хорошо подойдёт библиотека fuzzywuzzy. Она может посчитать на сколько один текст похож на другой или найти из списка строк самое похожее на заданный текст.
Пример поиска имени месяца по введённому юзером тексту. Список месяцев берём из примера прошлого поста.
>>> from fuzzywuzzy import processКонечно, на этом возможности не ограничены. Можно искать в текстах плагиат, анализировать текст на статистику похожих слов, создать автокомплиты с исправлениями в текстовых редакторах и тд.
>>> import calendar
>>> months = list(calendar.month_name)[1:]
# достаём все совпадения. Функция возвращает само слово и степень похожести
>>> process.extract('январ', months, limit=3)
[('Январь', 91), ('Март', 44), ('Сентябрь', 40)]
# можно сразу взять самое похожее совпадение
>>> process.extractOne('фев', months)
('Февраль', 90)
#libs
PyPI
fuzzywuzzy
Fuzzy string matching in python
JSON — весьма удобный формат для передачи или хранения данных. Но у него есть одна особенность: по умолчанию он умеет сериализовать только стандартные типы данных Python, такие как int, float, list, dict и тд.
Как только появляется какой-либо класс мы терпим фиаско с ошибкой что-то вроде
Как сделать так, чтобы любой объект смог сериализоваться в JSON?
Обычный подход это создать свой класс-сериализатор, где и будет прописан алгоритм превращения объектов в строку, словарь или что-то обычное для JSON
Допустим, у меня есть мой класс:
#libs
Как только появляется какой-либо класс мы терпим фиаско с ошибкой что-то вроде
TypeError: Object of type MyClass is not JSON serializableПричём не поддерживаются даже стандартные классы типа datetime или re.Pattern 😢. Повезло лишь некоторым классам, которые предназначены для хранения данных, например namedtuple или defaultdict.
Как сделать так, чтобы любой объект смог сериализоваться в JSON?
Обычный подход это создать свой класс-сериализатор, где и будет прописан алгоритм превращения объектов в строку, словарь или что-то обычное для JSON
Допустим, у меня есть мой класс:
class MyClass:Создадим сериализатор который умеет понимать такой тип объекта. Его задача: сохранить имя типа и данные инстанса чтобы потом можно было восстановить объект обратно. Для этого переопределим метод default(), котоырй по умолчанию выбрасывает ошибку когда объект не поддерживается.
def __init__(self):
self.x = 100
class MySerializer(json.JSONEncoder):Пробуем закодировать объект в JSON используя мой сериализатор
def default(self, obj):
if isinstance(obj, MyClass):
return {'type': MyClass.__name__,
'data': obj.__dict__}
return super().default(obj)
>>> mc = MyClass()Отлично, теперь json понимает мой тип! 😎
>>> json.dumps(mc, cls=MySerializer)
'{"type": "MyClass", "data": {"x": 100}}'
#libs
В прошлом посте мы научили JSON понимать новый тип данных. Но что если придётся записывать в JSON много разных неподдерживаемых типов?
Описывать для каждого отдельный
А если нам не известно что именно придётся сериализовать и нужно поддерживать в принципе всё что угодно? Ну хотя бы как-то записать объект чтобы не было ошибки и чтобы объект записался в JSON более менее информативно.
В этом случае можно вызвать стандартные способы репрезентации объекта. Самое простое это функция repr(). И тут уж как повезёт с тем, как именно разработчик позаботился о таком поведении его класса.
Более описательный метод, это закинуть объект в словарь с именем класса и данными инстанса.
В общем, возвращаясь к примеру из прошлого поста, мы просто убираем проверку конкретного типа
Скорее всего найдутся люди, которые осудят такой подход.
Может выбрасывать ошибку в случае отсутствия метода
#libs #tricks
Описывать для каждого отдельный
if isinstance()? А если нам не известно что именно придётся сериализовать и нужно поддерживать в принципе всё что угодно? Ну хотя бы как-то записать объект чтобы не было ошибки и чтобы объект записался в JSON более менее информативно.
В этом случае можно вызвать стандартные способы репрезентации объекта. Самое простое это функция repr(). И тут уж как повезёт с тем, как именно разработчик позаботился о таком поведении его класса.
Более описательный метод, это закинуть объект в словарь с именем класса и данными инстанса.
В общем, возвращаясь к примеру из прошлого поста, мы просто убираем проверку конкретного типа
class MySerializer(json.JSONEncoder):Функция vars() аналогична обращению к атрибуту
def default(self, obj):
return {'type': obj.__class__.__name__,
'data': vars(obj)}
__dict__
Теперь любой ранее неизвестный объект будет успешно сериализован... или нет?>>> json.dumps(datetime.datetime.now(), cls=MySerializer)Так уж вышло, что не любой объект имеет атрибут
AttributeError: 'datetime.datetime' object has no attribute '__dict__
__dict__. В таком случае используем repr()class MySerializer(json.JSONEncoder):То есть мы пробуем забрать данные инстанса, и если не получается то вызываем стандартное строковое представление объекта в надежде что разработчики позаботились о нём.
def default(self, obj):
return {'type': obj.__class__.__name__,
'data': getattr(obj, '__dict__', repr(obj))}
>>> json.dumps(datetime.now(), cls=MySerializer)⚠️ PS: Не могу сказать что решение идеально. Такую неявную сериализацию можно сравнить с замалчиванием ошибок.
'{"type": "datetime", "data": "datetime.datetime(...)"}'
try:То есть в какой-то момент вы будете получать совершенно бесполезные данные и придётся искать где это происходит. Если не определён метод
do_something()
except:
pass
__repr__ то вы получите что-то вроде такого<__main__.MyClass object at 0x00000147CE48CBC8>Что с этим делать? Непонятно! 🤔
Скорее всего найдутся люди, которые осудят такой подход.
Может выбрасывать ошибку в случае отсутствия метода
__repr__ в классе?#libs #tricks
Еще раз про JSON
Для создания кастомной сериализации объектов в JSON не обязательно создавать класс-сериализатор. Достаточно указать функцию default() которую в свою очередь подать в виде лямбды.
За читаемость примера не ручаюсь но выйдет что-то вроде такого:
⚠️ Повторяю! В ситуации, когда данные неизвестны, такой подход может привести к непредсказуемому поведению! Лично я использовал его только для дебага, когда требовалось получить хоть что-то в виде JOSN а не ошибку.
Явное лучше чем неявное 😉
Кстати, обычная функция часто лучше чем лямбда 😬
Так что вам эта же функция в нормальном виде
Меня устроило бы добавление в спецификацию класса метода
Но пока этого нет (и будет ли?) мы по-прежнему добавляем в класс метод
#tricks #libs
Для создания кастомной сериализации объектов в JSON не обязательно создавать класс-сериализатор. Достаточно указать функцию default() которую в свою очередь подать в виде лямбды.
За читаемость примера не ручаюсь но выйдет что-то вроде такого:
json.dumps(my_data, default=lambda obj: {
'type': f'{obj.__class__.__module__}.{obj.__class__.__name__}',
'data': getattr(obj, '__dict__', repr(obj))})
В примере я добавил полный путь к классу включая имя модуля.⚠️ Повторяю! В ситуации, когда данные неизвестны, такой подход может привести к непредсказуемому поведению! Лично я использовал его только для дебага, когда требовалось получить хоть что-то в виде JOSN а не ошибку.
Явное лучше чем неявное 😉
Кстати, обычная функция часто лучше чем лямбда 😬
Так что вам эта же функция в нормальном виде
def default_hook(obj):Почему бы не организовать поддержку всех стандартных объектов Python в стандартном JSON-энкодере? Я думаю дело в неочевидности этого процесса. Как можно сериализовать float? Тут вполне очевидно. А как сериализовать datetime? Вот тут тысяча и один вариант как можно форматировать дату. Поэтому данный этап отадётся на откуп разработчику.
return {
'type': f'{obj.__class__.__module__}.'
f'{obj.__class__.__name__}',
'data': getattr(obj, '__dict__', repr(obj))
}
json.dumps(my_data, default=default_hook)
Меня устроило бы добавление в спецификацию класса метода
__json__ по аналогии с __fspath__, который использовался бы стандартным энкодером. Метод возвращал бы поддерживаемый для JSON объект. Тогда не требуется что-то указывать в функции dump() и наш класс может использоваться в других модулях, где код уже записан и вставить что-то в default м не можем.Но пока этого нет (и будет ли?) мы по-прежнему добавляем в класс метод
toJson() и вызываем его, отправляя в json.dump().#tricks #libs
Как разделить строку с shell-командой на отдельные аргументы в виде списка?
Если сделать просто сплит по пробелу то получим то что надо, кроме случаев со вставками текста с пробелами. Например так:
Кто читает мой канал давно, уже в курсе.
А что делать, если нужно обратное действие? Объединить аргументы из списка в строку и при этом добавить кавычки в аргумент с пробелами.
Конечно, если вы используете subprocess то он сам всё разрулит. Но если вам нужна именно команда одной строкой, то можно воспользоваться готовой функцией в том же subprocess.
Возможно кто-то спросит, а зачем соединять аргументы в строку если subprocess сам это сделает а os.system не наш путь?
Мне как-то потребовалось отправлять команду на удалённое выполнение и в API поддерживалось указание команды только строкой. Так что всякое бывает)
#libs #basic
Если сделать просто сплит по пробелу то получим то что надо, кроме случаев со вставками текста с пробелами. Например так:
>>> '-arg "I Am Groot"'.split(' ')
['-arg', '"I', 'Am', 'Groot"']
Чтобы учитывать текст в кавычках как единый аргумент можно воспользоваться функцией shlex.split()Кто читает мой канал давно, уже в курсе.
А что делать, если нужно обратное действие? Объединить аргументы из списка в строку и при этом добавить кавычки в аргумент с пробелами.
Конечно, если вы используете subprocess то он сам всё разрулит. Но если вам нужна именно команда одной строкой, то можно воспользоваться готовой функцией в том же subprocess.
>>> from subprocess import list2cmdlineОн также позаботится об экранировании уже имеющихся кавычек
>>> list2cmdline(['-arg', 'I Am Groot'])
'-arg "I Am Groot"'
>>> list2cmdline(['-arg', 'I Am "Groot"'])А вот так он может "схлопнуть" в команду JSON
'-arg "I Am \"Groot\""'
>>> list2cmdline(['--json', json.dumps({'key': 'value'})])
'--json "{\"key\": \"value\"}"'
_______________Возможно кто-то спросит, а зачем соединять аргументы в строку если subprocess сам это сделает а os.system не наш путь?
Мне как-то потребовалось отправлять команду на удалённое выполнение и в API поддерживалось указание команды только строкой. Так что всякое бывает)
#libs #basic
Telegram
Python Заметки
В посте про правильное использование аргумента shell упоминалось что в некоторых случаях атрибуты следует отправлять списком а не строкой. Что делать, если команда приходит именно строкой? Как её преобразовать в список?
Ответ очевиден
>>> cmd_str = 'ls …
Ответ очевиден
>>> cmd_str = 'ls …
Для тех кто пропустил релиз Qt6!
PySide6 уже доступен!
Для старта можно взять эти странички:
https://wiki.qt.io/Qt_for_Python
https://doc.qt.io/qtforpython/
https://doc.qt.io/qtforpython/tutorials/index.html
Там же есть ссылка на репозиторий примеров
https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples
#qt
PySide6 уже доступен!
Для старта можно взять эти странички:
https://wiki.qt.io/Qt_for_Python
https://doc.qt.io/qtforpython/
https://doc.qt.io/qtforpython/tutorials/index.html
Там же есть ссылка на репозиторий примеров
https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples
#qt
Как в простом скрипте выполнить отложенное действие или даже несколько?
Например, нам требуется удалить файл через 10 секунд после его создания, но при этом основной поток не должен просто остановиться на 10 секунд с помощью time.sleep().
Допустим, у нас также нет никаких асинхронных очередей задач типа rq или celery.
Самый простой способ это класс threading.Timer.
Это удобный способ выполнить отложенное действие, которое не блокирует основной поток. А так же это пример как писать многопоточный код на базе класса threading.Thread. Всё что делает этот класс, это создаёт отдельный поток, в котором и будет запущена функция ожидания.
Тут у нас два варианта:
🔸 Если поток запущен как Daemon то он завершится вместе с программой, тем самым отменив выполнение колбека.
🔸 В противном случае программа не завершится пока поток не завершится.
По умолчанию режим Daemon отключен, то есть программа в любом случае дождётся исполнения колбека и только потом завершится.
Переключение режима следует делать ДО запуска потока!
Например, нам требуется удалить файл через 10 секунд после его создания, но при этом основной поток не должен просто остановиться на 10 секунд с помощью time.sleep().
Допустим, у нас также нет никаких асинхронных очередей задач типа rq или celery.
Самый простой способ это класс threading.Timer.
Это удобный способ выполнить отложенное действие, которое не блокирует основной поток. А так же это пример как писать многопоточный код на базе класса threading.Thread. Всё что делает этот класс, это создаёт отдельный поток, в котором и будет запущена функция ожидания.
from threading import TimerМожно проверить выполнилось ли действие с помощью неблокирующго метода ивента is_set()
import os
filename = '/home/user/data.txt'
t = Timer(10, lambda: os.remove(filename))
t.start()
if t.finished.is_set():или отменить выполнение
# do something
t.cancel()А что будет если процесс интерпретатора завершится ДО завершения таймаута?
Тут у нас два варианта:
🔸 Если поток запущен как Daemon то он завершится вместе с программой, тем самым отменив выполнение колбека.
🔸 В противном случае программа не завершится пока поток не завершится.
По умолчанию режим Daemon отключен, то есть программа в любом случае дождётся исполнения колбека и только потом завершится.
Переключение режима следует делать ДО запуска потока!
t = Timer(...)#tricks
t.setDaemon(True)
t.start()
Всех с Новым 2021🎉
Желаю всем нам чтобы год 2021 был сильно лучше чем 2020❗️
Между тем, ровно год назад был создан этот канал. Так что у нас тут немножко день рождения))) 🎂
Кажется пора подумать о новом контенте для канала 😉
#offtop
Желаю всем нам чтобы год 2021 был сильно лучше чем 2020❗️
Между тем, ровно год назад был создан этот канал. Так что у нас тут немножко день рождения))) 🎂
Кажется пора подумать о новом контенте для канала 😉
#offtop
Помните пост про абсолютный импорт? Он мне пригодился на днях, когда я объяснял особенности импортов в Python3.
В процессе объяснения собрался небольшой конспект с заметками. Давайте рассмотрим их в следующих постах.
В процессе объяснения собрался небольшой конспект с заметками. Давайте рассмотрим их в следующих постах.
Telegram
Python Заметки
Вторая по частоте future-функция, которую я использовал, это абсолютный импорт
from __future__ import absolute_import
Что она делает?
Изменения, которые вносит эта инъекция описаны в PEP328
Покажу простой пример.
Допустим, есть такой пакет:
/my_package…
from __future__ import absolute_import
Что она делает?
Изменения, которые вносит эта инъекция описаны в PEP328
Покажу простой пример.
Допустим, есть такой пакет:
/my_package…
Подразумеваемые неймспейсы или неявные пакеты.
Этот функционал добавлен в Python 3.3
Что он означает?
Ранее, до 3.3 пакетами считались лишь директории, в которых есть файл
Этот файл одновременно являлся свидетельством того, что директория это Python-пакет, и служил "телом" этого пакета. То есть местом, где можно написать код, как это делается внутри модуля. Этот код исполняется в момент импорта пакета, так что его принято называть "код инициализации пакета".
Начиная с версии 3.3 Любая директория считается пакетом и Python будет пытаться использовать любую директорию для импорта.
Конечно, не любую в файловой системе, а только те что находятся в sys.path.
Это значит, что теперь
🔸 вам требуется создать код инициализации пакета
🔸 нужна совместимость со старыми версиями Python
На мой взгляд это немного упрощает разработку, делает её чище, но с другой стороны убивает некоторую однозначность происходящего.
Например, я создал репозиторий со своей библиотекой и рядом положил код примеров или тестов.
Конечно, пример несколько надуманный. Никто не будет добавлять корень репозитория в sys.path. Но, я думаю, суть ясна. Иногда директория это просто директория а не пакет!
#basic #pep
Этот функционал добавлен в Python 3.3
Что он означает?
Ранее, до 3.3 пакетами считались лишь директории, в которых есть файл
__init__.py.Этот файл одновременно являлся свидетельством того, что директория это Python-пакет, и служил "телом" этого пакета. То есть местом, где можно написать код, как это делается внутри модуля. Этот код исполняется в момент импорта пакета, так что его принято называть "код инициализации пакета".
Начиная с версии 3.3 Любая директория считается пакетом и Python будет пытаться использовать любую директорию для импорта.
Конечно, не любую в файловой системе, а только те что находятся в sys.path.
Это значит, что теперь
__init__.py нужно делать только если:🔸 вам требуется создать код инициализации пакета
🔸 нужна совместимость со старыми версиями Python
На мой взгляд это немного упрощает разработку, делает её чище, но с другой стороны убивает некоторую однозначность происходящего.
Например, я создал репозиторий со своей библиотекой и рядом положил код примеров или тестов.
repo_name/
my_library/
__init__.py
main.py
examples/
exam1.py
exam2.py
В этом репозитории пакетом является только my_library, остальные директории это не пакеты, это просто дополнительный код в файлах. Директория examples не добавлена в sys.path, в ней нет рабочих модулей. Но если она лежит рядом с my_library, то Python вполне сможет импортнуть из неё модули, так как посчитает что examples это валидный пакет.Конечно, пример несколько надуманный. Никто не будет добавлять корень репозитория в sys.path. Но, я думаю, суть ясна. Иногда директория это просто директория а не пакет!
#basic #pep
Python Enhancement Proposals (PEPs)
PEP 420 – Implicit Namespace Packages | peps.python.org
Namespace packages are a mechanism for splitting a single Python package across multiple directories on disk. In current Python versions, an algorithm to compute the packages __path__ must be formulated. With the enhancement proposed here, the import ...
Первая директория в sys.path
🔸 Когда вы запускаете Python-интерпретатор в интерактивном режиме, в системные пути (sys.path) в самое начало добавляется текущая рабочая директория
🔸 Если вы запускаете интерпретатор передавая скрипт как аргумент, то история получается иная. На первом месте будет директория в которой располагается скрипт. А текущая рабочая директория игнорируется.
Пишем скрипт с таким содержанием:
На что это влияет?
На видимость модулей для импорта. Если вы ждёте, что, запустив скрипт по пути, сможете импортировать модули из текущей рабочей директории, то вы ошибаетесь. Придётся добавлять путь os.getcwd() в sys.path самостоятельно или заранее объявлять переменную PYTHONPATH.
#basic
🔸 Когда вы запускаете Python-интерпретатор в интерактивном режиме, в системные пути (sys.path) в самое начало добавляется текущая рабочая директория
>>> for path in sys.path:
... print(f'"{path}"')
""
"/usr/lib/python37.zip"
"/usr/lib/python3.7"
...
Первая строка пустая, что и означает текущую рабочую директорию.🔸 Если вы запускаете интерпретатор передавая скрипт как аргумент, то история получается иная. На первом месте будет директория в которой располагается скрипт. А текущая рабочая директория игнорируется.
Пишем скрипт с таким содержанием:
# script.py
import sys
for path in sys.path:
print(f'"{path}"')
Запускаемpython3 /home/user/dev/script.py
Получаем"/home/user/dev"
"/usr/lib/python37.zip"
"/usr/lib/python3.7"
...
🔸 Если вы запускаете скрипт по имени модуля то на первом месте будет домашняя директория текущего юзераpython3 -m script
"/home/user"
"/usr/lib/python37.zip"
"/usr/lib/python3.7"
...
Скрипт должен быть доступен для импортаНа что это влияет?
На видимость модулей для импорта. Если вы ждёте, что, запустив скрипт по пути, сможете импортировать модули из текущей рабочей директории, то вы ошибаетесь. Придётся добавлять путь os.getcwd() в sys.path самостоятельно или заранее объявлять переменную PYTHONPATH.
#basic
Многие из тех кто активно работал с Python2 несколько удивлены, почему в Python3 удобная функция reload() переехала из builtin в imp а потом и в importlib?
Ну было же удобно! А теперь лишний импорт😖
Дело в том, что начиная с Python3.3 функция reload() переписана на Python вместо Cи.
Что это нам даёт?
🔸 Такой код проще поддерживать и развивать
🔸 Python код легче читать, изучать и понимать.
Сравните это ➡️ и это ➡️.
🔸 Как результат пункта 2, проще писать свои расширения импорта. Например, пользовательский импортёр с какой-либо хитрой логикой по аналогии с импортом из zip архивов.
А есть ли у этого решения недостатки? Да, они всегда есть.
🔹 Так как это не builtin функция, её следует импортнуть перед использованием
🔹 Скорость замедлилась примерно на 5%. Очевидно, что это совершенно не критично. К тому же от версии к версии логика импорта будет оптимизироваться и ускоряться.
В самом начале файла
#basic
Ну было же удобно! А теперь лишний импорт😖
Дело в том, что начиная с Python3.3 функция reload() переписана на Python вместо Cи.
Что это нам даёт?
🔸 Такой код проще поддерживать и развивать
🔸 Python код легче читать, изучать и понимать.
Сравните это ➡️ и это ➡️.
🔸 Как результат пункта 2, проще писать свои расширения импорта. Например, пользовательский импортёр с какой-либо хитрой логикой по аналогии с импортом из zip архивов.
А есть ли у этого решения недостатки? Да, они всегда есть.
🔹 Так как это не builtin функция, её следует импортнуть перед использованием
🔹 Скорость замедлилась примерно на 5%. Очевидно, что это совершенно не критично. К тому же от версии к версии логика импорта будет оптимизироваться и ускоряться.
В самом начале файла
importlib/__init__.py мы видим такой импорт:import _imp # Just the builtin component, NOT the full Python moduleТо есть часть функционала по прежнему написана на Си, но достаточно низкоуровневая.
#basic
GitHub
cpython/import.c at 2.7 · python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
Вопросы про переменную PYTHONPATH
🔸 Как она определяет пути поиска модулей при импорте?
Пути поиска модулей находятся в списке sys.path. Как формируется этот список?
Исходя из документации мы может выделить 3 основных этапа.
▫️ Путь к запускаемому скрипту или рабочая директория
▫️ Переменная PYTHONPATH
▫️ Стандартные пути к библиотекам
Это значит, что все три этапа выполняются в момент инициализации интерпретатора. Результат заполняет список sys.path. В том числе и пути, указанные в переменной PYTHONPATH.
🔸 Можно ли добавлять новые пути в эту переменную в Python-коде?
Можно, но учитывая, что используется она только во время старта интерпретатора, никакого эффекта это иметь не будет.
Для изменения путей поиска модулей в коде нужно изменять непосредственно список sys.path.
🔸 Можно ли указать много путей для поиска?
Да, с помощью переменной PYTHONPATH можно указать несколько директорий, разделённых символом разделения пути. Для Linux это символ ":", для Windows это ";".
Например:
🔸 Как она определяет пути поиска модулей при импорте?
Пути поиска модулей находятся в списке sys.path. Как формируется этот список?
Исходя из документации мы может выделить 3 основных этапа.
▫️ Путь к запускаемому скрипту или рабочая директория
▫️ Переменная PYTHONPATH
▫️ Стандартные пути к библиотекам
Это значит, что все три этапа выполняются в момент инициализации интерпретатора. Результат заполняет список sys.path. В том числе и пути, указанные в переменной PYTHONPATH.
🔸 Можно ли добавлять новые пути в эту переменную в Python-коде?
Можно, но учитывая, что используется она только во время старта интерпретатора, никакого эффекта это иметь не будет.
Для изменения путей поиска модулей в коде нужно изменять непосредственно список sys.path.
🔸 Можно ли указать много путей для поиска?
Да, с помощью переменной PYTHONPATH можно указать несколько директорий, разделённых символом разделения пути. Для Linux это символ ":", для Windows это ";".
Например:
export PYTHONPATH=/mnt/libs:~/mylibs
#basic #tricksTelegram
Python Заметки
Первая директория в sys.path
🔸 Когда вы запускаете Python-интерпретатор в интерактивном режиме, в системные пути (sys.path) в самое начало добавляется текущая рабочая директория
>>> for path in sys.path:
... print(f'"{path}"')
""
"/usr/lib/python37.zip"…
🔸 Когда вы запускаете Python-интерпретатор в интерактивном режиме, в системные пути (sys.path) в самое начало добавляется текущая рабочая директория
>>> for path in sys.path:
... print(f'"{path}"')
""
"/usr/lib/python37.zip"…
Мы уже знаем, что на текущую сессию интерпретатора изменение PYTHONPATH никак не повлияет. Но если вы запустите дочерний процесс, то он унаследует окружение текущего процесса, а значит и изменения в любых переменных будут на него влиять.
Вот небольшой пример:
Объявляем переменную
Лучшей практикой является передача энвайронмента явно через аргумент env!
#basic
Вот небольшой пример:
Объявляем переменную
user@host:~$ export PYTHONPATH=/path1Запускаем интерпретатор
user@host:~$ python3Проверим что в sys.path
>>> import sysДобавляем что-то в переменную
>>> print(sys.path)
['', '/path1', '/usr/lib/...', ...]
>>> import osИзменений нет. Но давайте запустим дочерний процесс и посмотрим там
>>> os.emviron['PYTHONPATH'] = '/path1:/path2'
>>> print(sys.path)
['', '/path1', '/usr/lib/...', ...]
>>> os.system('python3')
# теперь мы находимся в другом процессе
>>> import sys
>>> print(sys.path)
['', '/path1', '/path2', '/usr/lib/...', ...]
Тоже самое будет и с subprocess, так как по умолчанию текущее окружение тоже наследуется.>>> import subprocess______________________
>>> subprocess.call(['python3', '-c', 'import sys;print(sys.path)'])
['', '/path1', '/path2', '/usr/lib/...', ...]
Лучшей практикой является передача энвайронмента явно через аргумент env!
import subprocessЭто поможет точно понимать какое окружение будет у запускаемого процесса и при этом не изменять окружение текущего процесса.
subprocess.call(cmd, env={'PYTHONPATH': '...'})
#basic
Релятивный импорт в исполняемом файле
Часто встречается ситуация, когда исполняемый скрипт находится внутри Python-пакета. Например, представим такую структуру библиотеки:
Но что, если я внутри этого файла хочу импортировать модуль
Есть три способа как избежать этой ошибки. Все они требуют чтобы библиотека
🔸Просто пишем полный путь импорта
🔸 Если интерпретатору подсказать имя пакета в котором мы находимся, то всё заведётся. И есть два способа это сделать. Первый способ — это запускать не через имя файла а по имени модуля
🔸 Если предыдущий способ недоступен (то есть запускаем именно по пути к файлу .../start.py), то объявляем имя пакета прямо внутри кода. Для этого используем переменную модуля
🔹 динамически определить имя пакета в котором находимся
🔹 добавить необходимые пути к основной библиотеке в sys.path перед импортом
🔹 переместить обновление
#tricks
Часто встречается ситуация, когда исполняемый скрипт находится внутри Python-пакета. Например, представим такую структуру библиотеки:
my_lib/
cmd/
start.py
stop.py
core.py
services.py
Для запуска каких-то процессов мне надо исполнить скрипт start.py и вот как я делаю его вызов:python3 /mnt/libs/my_lib/cmd/start.py
Пока выглядит всё красиво. Но что, если я внутри этого файла хочу импортировать модуль
services.py? При этом я хочу использовать релятивный импорт# start.py
if __name__ == "__main__":
from .. import services
Я получу такую ошибку:ValueError: attempted relative import beyond top-level package
Эта ошибка возникает потому, что интерпретатор просто не знает что мы находимся внутри пакета и не может понять куда это мы собрались выйти на уровень выше)Есть три способа как избежать этой ошибки. Все они требуют чтобы библиотека
my_lib находилась в доступном для импорта месте, то есть в моëм случае чтобы путь /mnt/libs был в sys.path.🔸Просто пишем полный путь импорта
if __name__ == "__main__":
from my_lib import services
Это сработает. Но, очевидно, что это не то, что мы ищем. Нам нужен релятивный импорт.🔸 Если интерпретатору подсказать имя пакета в котором мы находимся, то всё заведётся. И есть два способа это сделать. Первый способ — это запускать не через имя файла а по имени модуля
python -m my_lib.cmd.start
Уже самой командой мы обозначили все необходимые неймспейсы.🔸 Если предыдущий способ недоступен (то есть запускаем именно по пути к файлу .../start.py), то объявляем имя пакета прямо внутри кода. Для этого используем переменную модуля
__package__
if __name__ == "__main__":
if __package__ is None:
__package__ = 'my_lib.cmd'
from .. import services
Кстати, мы также можем при необходимости:🔹 динамически определить имя пакета в котором находимся
🔹 добавить необходимые пути к основной библиотеке в sys.path перед импортом
🔹 переместить обновление
__package__ в начале скрипта вместе со всеми импортами но обязательно с проверкой is None!#tricks
Бывает начинающие в процессе обучения создают файлы с именем модуля который они изучают. В результате на тестовых запусках ничего не работает😱
Всё потому, что появилась коллизия имён. Например, изучаете вы модуль datetime, и создаёте с таким именем файл (ну логично же 😄) прямо в рабочей директории.
Потом, при попытке импортировать datetime модуль, из-за приоритета импорта будет импортирован файл из рабочий директории а не оригинальная библиотека. Ведь имя файла это суть имя модуля!
А знаете ли вы, что не все стандартные модули можно так перезаписать? Коллизии имён не подвержены builtin модули. Они всегда стоят на первом месте в приоритете импорта, поэтому их нельзя заменить.
Полный список таких модулей можно посмотреть в списке
То есть, вы сломаете весь Python если назовёте свой модуль os или site, но если назовёте time или gc то ничего страшного не случится)))
Тем не менее, никогда не называйте модули уже занятыми именами!!! ⚠️
Я всегда рекомендую всем своим файлам делать именной префикс из 2-3 символов. Например я называю свои проекты так:
#tricks #basic
Всё потому, что появилась коллизия имён. Например, изучаете вы модуль datetime, и создаёте с таким именем файл (ну логично же 😄) прямо в рабочей директории.
Потом, при попытке импортировать datetime модуль, из-за приоритета импорта будет импортирован файл из рабочий директории а не оригинальная библиотека. Ведь имя файла это суть имя модуля!
А знаете ли вы, что не все стандартные модули можно так перезаписать? Коллизии имён не подвержены builtin модули. Они всегда стоят на первом месте в приоритете импорта, поэтому их нельзя заменить.
Полный список таких модулей можно посмотреть в списке
sys.builtin_module_names. То есть, вы сломаете весь Python если назовёте свой модуль os или site, но если назовёте time или gc то ничего страшного не случится)))
Тем не менее, никогда не называйте модули уже занятыми именами!!! ⚠️
Я всегда рекомендую всем своим файлам делать именной префикс из 2-3 символов. Например я называю свои проекты так:
pw_project_nameЛибо под ситуацию
pw_ui_tools.py
pw_something/main.py
tst_scriptname.pyИ искать проще, и коллизий нет.
(не "test" чтобы не подхватывал pytest)
dbg_script.py
maya_ui.py
hou_menu_tools.py
#tricks #basic
Telegram
Python Заметки
Первая директория в sys.path
🔸 Когда вы запускаете Python-интерпретатор в интерактивном режиме, в системные пути (sys.path) в самое начало добавляется текущая рабочая директория
>>> for path in sys.path:
... print(f'"{path}"')
""
"/usr/lib/python37.zip"…
🔸 Когда вы запускаете Python-интерпретатор в интерактивном режиме, в системные пути (sys.path) в самое начало добавляется текущая рабочая директория
>>> for path in sys.path:
... print(f'"{path}"')
""
"/usr/lib/python37.zip"…
Как получить минимальную информацию о модули не импортируя сам модуль?
Стандартная библиотека pyclbr позволяет это сделать. Она не импортит модуль, а только парсит код и возвращает список имеющихся в модуле классов и функций в виде специальных объектов. Например, вы сможете узнать какие в модуле есть классы, от чего они наследованы и какие у них методы.
Возьмём для примера такой простой модуль
Стандартная библиотека pyclbr позволяет это сделать. Она не импортит модуль, а только парсит код и возвращает список имеющихся в модуле классов и функций в виде специальных объектов. Например, вы сможете узнать какие в модуле есть классы, от чего они наследованы и какие у них методы.
Возьмём для примера такой простой модуль
# mymodule.py
class Cls1:
def __init__(self):
pass
def execute(self):
pass
class Cls2(Cls1):
pass
def start():
pass
Запускаем анализ>>> import pyclbr
>>> mdata = pyclbr.readmodule_ex('mymodule')
# список всего что нашлось
>>> print(mdata)
{'Cls1': <pyclbr.Class object at 0x000001B62F9D4288>,
'Cls2': <pyclbr.Class object at 0x000001B62F9DD908>,
'start': <pyclbr.Function object at 0x000001B62F8A8288>}
# список методов класса (имя метода и строка объявления)
>>> mdata['Cls1'].methods
{'__init__': 3, 'execute': 6}
# получения наследуемых классов
>>> mdata['Cls2'].super
[<pyclbr.Class object at 0x000001B62F9D4288>]
>>> mdata['Cls2'].super[0].name
'Cls1'
#libs #tricks