В прошлом примере мы добились совпадения хеш-суммы двух архивов. Но не даёт покоя тот факт, что делается это слишком долго. Давайте сравним скорость.
Кажется что для 100 итераций это время нормальное, но представьте что архив будет размером не 50 Мб а 10Гб. Время возрастёт серьезно!
Попробуем сократить разрыв. Давайте считать не все данные а только хеш файлов, который посчитает сам модуль tarfile.
Почему так, можно почитать в комментарии. Если коротко, мы считываем только заголовки элементов архива. Но на сколько я понял, это не отменяет чтение всего буфера из архива.
А можно быстрей? Можно...
#libs #tricks
>>> from timeit import timeit
>>> timeit("hashlib.md5(
open('archive1.tar.gz', 'rb').read()
).hexdigest()", number=100, globals=globals())
# 8.6
>>> timeit("get_hash_tar1('archive1.tar.gz')",
number=100, globals=globals())
# 29.8
Разница больше чем в 3 раза! Видимо потому, что кроме простого чтения байтиков мы еще применяем алгоритм разжатия данных?Кажется что для 100 итераций это время нормальное, но представьте что архив будет размером не 50 Мб а 10Гб. Время возрастёт серьезно!
Попробуем сократить разрыв. Давайте считать не все данные а только хеш файлов, который посчитает сам модуль tarfile.
def get_hash_tar2(path):
hsum = hashlib.md5()
with tarfile.open(path) as tar:
for file in tar.getmembers():
hsum.update(file.chksum.to_bytes(8, byteorder='big'))
return hsum.hexdigest()
>>> timeit.timeit("get_hash_tar2('archive1.tar.gz')",
number=100, globals=globals())
11.5
Прирост скорости x3! Уже неплохо, почти как просчет хеша для архива без разжатия. Почему так, можно почитать в комментарии. Если коротко, мы считываем только заголовки элементов архива. Но на сколько я понял, это не отменяет чтение всего буфера из архива.
А можно быстрей? Можно...
#libs #tricks
GitHub
cpython/Lib/tarfile.py at main · python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
На самом деле архивы TAR оказались менее удобными в нашей теме проверки идентичности. Давайте сделаем всё тоже самое для ZIP.
Допустим, тестовые файлы мы уже создали используя код из прошлого поста.
Теперь создадим архивы.
Ну всё, на этом расходимся...
Хотя подождите ка, часто ли вы проверяете один и тот же файл на идентичность?
Давайте имитируем ситуацию когда файл был перезаписан или "модифицирован" но при этом фактически не изменился. То есть изменились только его атрибуты. Для этого можно использовать Linux-команду touch, которая обновляет время последнего доступа к файлу.
Давайте теперь сделаем функцию для проверки файлов внутри архивов, которая считывает непосредственно данные файлов в разжатом виде.
А что по времени?
⭐️ А теперь самая главная фишка ZIP - при создании архива он СРАЗУ записывает контрольную сумму файла в заголовки!
А это значит что мы можем просто считать готовые хеш-суммы и сравнить их! Это называется CRC (cyclic redundancy check)
🌐 Полный листинг тестов в Jupyter (для экспериментов жмём Open in Colab)
📌 И просто в Gists
#libs #tricks
Допустим, тестовые файлы мы уже создали используя код из прошлого поста.
Теперь создадим архивы.
import zipfile
def create_zip(archive_path, files):
with zipfile.ZipFile(archive_path, "w") as zf:
for file in files:
zf.write(file)
create_zip('archive1.zip', files_to_archive)
create_zip('archive2.zip', files_to_archive)
Проверим хеш>>> hashlib.md5(open("archive1.zip", "rb").read()).hexdigest()
'd54670be5e01e483797ee4ae30089423'
>>> hashlib.md5(open("archive2.zip", "rb").read()).hexdigest()
'd54670be5e01e483797ee4ae30089423'
Отлично! ZIP создаёт одинаковые архивы и сразу выдаёт одинаковую хеш-сумму! Ну всё, на этом расходимся...
Хотя подождите ка, часто ли вы проверяете один и тот же файл на идентичность?
Давайте имитируем ситуацию когда файл был перезаписан или "модифицирован" но при этом фактически не изменился. То есть изменились только его атрибуты. Для этого можно использовать Linux-команду touch, которая обновляет время последнего доступа к файлу.
touch example_file0.txt
touch example_file1.txt
...
Либо альтернативу на Pythonfrom pathlib import Path
for f in files_to_archive:
Path(f).touch()
Содержимое файлов не изменилось! Но изменились атрибуты. Пересоздаём второй архив.create_zip('archive2.zip', files_to_archive)
Проверяем>>> hashlib.md5(open("archive1.zip", "rb").read()).hexdigest()
'd54670be5e01e483797ee4ae30089423'
>>> hashlib.md5(open("archive2.zip", "rb").read()).hexdigest()
'aa508dbba4e223abe45e16dba4ad6e1f'
Вот это более правдивая ситуация.Давайте теперь сделаем функцию для проверки файлов внутри архивов, которая считывает непосредственно данные файлов в разжатом виде.
def get_hash_zip(path):
hash_md5 = hashlib.md5()
with zipfile.ZipFile(path, "r") as z:
for f_name in z.namelist():
with z.open(f_name) as f:
hash_md5.update(f.read())
return hash_md5.hexdigest()
Сравним теперь хеш-суммы архивов>>> get_hash_zip('archive1.zip')
'0b27c443737b0a84381b827e1d9a913b'
>>> get_hash_zip('archive2.zip')
'0b27c443737b0a84381b827e1d9a913b'
Всё чётко сработало! А что по времени?
>>> timeit.timeit("get_hash_zip('archive1.zip')", number=100, globals=globals())
10.8
Ну тоже неплохо.⭐️ А теперь самая главная фишка ZIP - при создании архива он СРАЗУ записывает контрольную сумму файла в заголовки!
А это значит что мы можем просто считать готовые хеш-суммы и сравнить их! Это называется CRC (cyclic redundancy check)
def get_hash_zip2(path):
h = hashlib.md5()
for info in zipfile.ZipFile(path).infolist():
h.update(info.CRC.to_bytes(8, byteorder='big'))
return h.hexdigest()
>>> timeit.timeit("get_hash_zip2('archive1.zip')", number=100, globals=globals())
0.008
То есть даже буфер никакой не считывается, только несколько байт из заголовков каждого файла. Выполняется моментально. В моем случае 100 итераций за 8 мс! 🌐 Полный листинг тестов в Jupyter (для экспериментов жмём Open in Colab)
📌 И просто в Gists
#libs #tricks
Telegram
Python Заметки
Для проверки целостности или идентичности файлов всегда используется проверка контрольной суммы.
Это работает в большинстве случаев, но не всегда. Давайте сделаем простой тест.
Создадим несколько рандомных файлов
import os
# create random test files
files_to_archive…
Это работает в большинстве случаев, но не всегда. Давайте сделаем простой тест.
Создадим несколько рандомных файлов
import os
# create random test files
files_to_archive…
🔖 Подводя итоги по прошлым постам плюс пара заметок:
🔸 Если требуется проверять идентичность содержимого архивов, лучше использовать ZIP и проверять только CRC.
🔸 Указанный способ может помочь проверить отдельные файлы в архиве с возможностью перекачать только новые а не весь архив
🔸 Для проверки хеш-суммы файла можно использовать утилиту md5sum (она не умеет проверять хеш внутри ахрхивов)
#libs #tricks
🔸 Если требуется проверять идентичность содержимого архивов, лучше использовать ZIP и проверять только CRC.
🔸 Указанный способ может помочь проверить отдельные файлы в архиве с возможностью перекачать только новые а не весь архив
🔸 Для проверки хеш-суммы файла можно использовать утилиту md5sum (она не умеет проверять хеш внутри ахрхивов)
# Linux:(находится в директории скриптов /Tools/scripts/md5sum.py)
md5sum filename
# Windows:
python md5sum.py filename
#libs #tricks
Если вы писали когда-либо мультипоточные или мультипроцессорные приложения то вы знаете какая самая большая проблема у таких программ.
Это конечно же синхронизация (мы сейчас не про GIL).
Что такое синхронизация? Это когда параллельно работающий код может делать расчёты абсолютно независимо, но вот к общим данным они должны обращаться последовательно в определённом порядке. То есть нужна синхронность вместо асинхронности.
Пока один процесс открыл файл, другие просто ждут своей очереди. Пока один поток обновляет переменную, другие просто спят, опять же ждут своей очереди.
В Python есть стандартные средства для синхронизации. Это так называемые мьютексы с различной логикой. Например Lock, Semaphore или очередь Queue. Все они работают в контексте одного интерпретатора. То есть все потоки или процессы должны быть запущены из одной программы. Тогда можно использовать один Lock между потоками и общую память между процессами.
Но что же делать, если процессы независимы? То есть я просто запускаю два разных интерпретатора с разным кодом. Возможно даже разных версий Python. В моём случае мне потребовалось писать некоторый кеш из одного модуля, который работает совершенно в разных приложениях как вспомогательный инструмент.
Сначала я пытался что-то сделать на основе shelve, но потом нашел отличную библиотеку diskcache.
Этот проект покрыл все мои потребности:
▫️ thread-safe и process-safe
То есть можно выполнять команды из разных процессов и потоков и они всегда будут синхронизированы.
Можно писать из разных процессов не опасаясь получить ошибку о том что файл занят другим процессом (как это бывает с shelve)
▫️всегда атомарные операции
Это значит что любое действие выполняется в один запрос.
▫️ безсерверный
Не требуется отдельный процесс для синхронизации. Всё решается через базу данных.
Полный список возможностей
Из удобных фичей можно еще отметить встроенный Lock, позволяющий синхронизировать независимые процессы. Это как раз то что я искал!
На что стоит обратить внимание:
🔸 несовместимы версии python 2 и 3 из-за разницы протоколов pickle
🔸 надёжность работы с сетевыми дисками под вопросом, я бы не стал. Но тут скорей вопрос к сети чем к софту.
🔸 при создании инстанса
#libs
Это конечно же синхронизация (мы сейчас не про GIL).
Что такое синхронизация? Это когда параллельно работающий код может делать расчёты абсолютно независимо, но вот к общим данным они должны обращаться последовательно в определённом порядке. То есть нужна синхронность вместо асинхронности.
Пока один процесс открыл файл, другие просто ждут своей очереди. Пока один поток обновляет переменную, другие просто спят, опять же ждут своей очереди.
В Python есть стандартные средства для синхронизации. Это так называемые мьютексы с различной логикой. Например Lock, Semaphore или очередь Queue. Все они работают в контексте одного интерпретатора. То есть все потоки или процессы должны быть запущены из одной программы. Тогда можно использовать один Lock между потоками и общую память между процессами.
Но что же делать, если процессы независимы? То есть я просто запускаю два разных интерпретатора с разным кодом. Возможно даже разных версий Python. В моём случае мне потребовалось писать некоторый кеш из одного модуля, который работает совершенно в разных приложениях как вспомогательный инструмент.
Сначала я пытался что-то сделать на основе shelve, но потом нашел отличную библиотеку diskcache.
Этот проект покрыл все мои потребности:
▫️ thread-safe и process-safe
То есть можно выполнять команды из разных процессов и потоков и они всегда будут синхронизированы.
Можно писать из разных процессов не опасаясь получить ошибку о том что файл занят другим процессом (как это бывает с shelve)
▫️всегда атомарные операции
Это значит что любое действие выполняется в один запрос.
▫️ безсерверный
Не требуется отдельный процесс для синхронизации. Всё решается через базу данных.
Полный список возможностей
Из удобных фичей можно еще отметить встроенный Lock, позволяющий синхронизировать независимые процессы. Это как раз то что я искал!
На что стоит обратить внимание:
🔸 несовместимы версии python 2 и 3 из-за разницы протоколов pickle
🔸 надёжность работы с сетевыми дисками под вопросом, я бы не стал. Но тут скорей вопрос к сети чем к софту.
🔸 при создании инстанса
diskcache.Cache() все данные пишутся в рандомную директорию в temp. Чтобы синхронизировать разные процессы следует указывать одинаковый путь diskcache.Cache(some_path).#libs
GitHub
GitHub - grantjenks/python-diskcache: Python disk-backed cache (Django-compatible). Faster than Redis and Memcached. Pure-Python.
Python disk-backed cache (Django-compatible). Faster than Redis and Memcached. Pure-Python. - grantjenks/python-diskcache
Возможно, стоит пояснить разницу между синхронизацией из thread/process-safe и синхронизацией с помощью Lock 🤔
Наша задача — заставить разные процессы и потоки обращаться к базе данных (или любым другим ресурсам) последовательно. Чтобы не случилось так называемого race condition, то есть состояние гонки. Это когда разные потоки или процессы пытаются одновременно что-то сделать с одним и тем же ресурсом.
В этом случае нам нужна какая-то логика ограничения. Пока один процесс не завершил своё действие, другие не могут получить доступ к ресурсу.
Так вот, thread-safe и process-safe означает что отдельно взятые операции записи в БД гарантированно будут последовательны. Запросы из разных процессов или потоков выстроятся в очередь и не будут мешать друг другу. Лучше всего когда этот блок реализован на уровне БД в виде атомарных операций или ещё как-то.
Но зачем нам тогда еще дополнительный Lock?
Этот способ синхронизации используется когда процесс никак не укладывается в одно действие и должен сделать множество операций прежде чем дать доступ следующему. В этом случае процесс ставит некий глобальный Lock на ресурс и никто другой, даже получив законное право на доступ, не может ничего сделать. Все ждут пока этот Lock не будет снят.
Это решается на уровне приложения и правильность реализации полностью в вашей ответственности. Например, если забыли разблокировать или сделали перекрёстный Lock (Deadlock как на картинке), то всё зависнет в бесконечном ожидании.
#basic
Наша задача — заставить разные процессы и потоки обращаться к базе данных (или любым другим ресурсам) последовательно. Чтобы не случилось так называемого race condition, то есть состояние гонки. Это когда разные потоки или процессы пытаются одновременно что-то сделать с одним и тем же ресурсом.
В этом случае нам нужна какая-то логика ограничения. Пока один процесс не завершил своё действие, другие не могут получить доступ к ресурсу.
Так вот, thread-safe и process-safe означает что отдельно взятые операции записи в БД гарантированно будут последовательны. Запросы из разных процессов или потоков выстроятся в очередь и не будут мешать друг другу. Лучше всего когда этот блок реализован на уровне БД в виде атомарных операций или ещё как-то.
Но зачем нам тогда еще дополнительный Lock?
Этот способ синхронизации используется когда процесс никак не укладывается в одно действие и должен сделать множество операций прежде чем дать доступ следующему. В этом случае процесс ставит некий глобальный Lock на ресурс и никто другой, даже получив законное право на доступ, не может ничего сделать. Все ждут пока этот Lock не будет снят.
Это решается на уровне приложения и правильность реализации полностью в вашей ответственности. Например, если забыли разблокировать или сделали перекрёстный Lock (Deadlock как на картинке), то всё зависнет в бесконечном ожидании.
#basic
От многопоточных вычислений переходим к распределённым. То есть вычисления, происходящие на нескольких компьютерах.
Конечно, в зависимости от задачи, вы можете взять готовые решения вроде CGRU или Deadline для рендеринга, charm4py или Dask для ML, или замутить что-то на AWS С2. Но хотелось бы чего-то попроще, попитоничней что ли)
А ведь в Python есть средства "из коробки" для синхронизации нескольких процессов на разных хостах.
Вот простой пример кода, который синхронизирует работу двух процессов на разных компьютерах.
В этом случае используется процесс-посредник, который является синхронизирующим сервером.
В примере создаётся некий Manager, который шарит общую для клиентов очередь. Все подключившиеся могут что-то в неё писать или забирать.
В моём коде один процесс что-то "считает" и складывает в очередь, другой забирает и продолжает какие-то свои "расчёты".
Если у вас есть несколько машин, то можете попробовать это запустить по сети (нужно заменить 'localhost' на IP-адрес сервера). Но и на локальной машине сработает.
Gist 🌎
#libs #source #tricks
Конечно, в зависимости от задачи, вы можете взять готовые решения вроде CGRU или Deadline для рендеринга, charm4py или Dask для ML, или замутить что-то на AWS С2. Но хотелось бы чего-то попроще, попитоничней что ли)
А ведь в Python есть средства "из коробки" для синхронизации нескольких процессов на разных хостах.
Вот простой пример кода, который синхронизирует работу двух процессов на разных компьютерах.
В этом случае используется процесс-посредник, который является синхронизирующим сервером.
В примере создаётся некий Manager, который шарит общую для клиентов очередь. Все подключившиеся могут что-то в неё писать или забирать.
В моём коде один процесс что-то "считает" и складывает в очередь, другой забирает и продолжает какие-то свои "расчёты".
Если у вас есть несколько машин, то можете попробовать это запустить по сети (нужно заменить 'localhost' на IP-адрес сервера). Но и на локальной машине сработает.
Gist 🌎
#libs #source #tricks
cgru.info
CGRU - Afanasy - Rules
CG Tools, Afanasy - Free Open Source Render Farm Manager, Rules - Project Tracker.
Самый большой минус синхронизации из прошлого поста - нестабильность. Не знаю как у вас а у меня эта штука падала несколько раз)
По моему не очень production-ready.
Что же делать? Отправлять disckcache на сетевой диск? Не, я бы не стал. Ведь есть отличная альтернатива! Это Redis.
Redis это жутко быстрая in-memory база данных. Запись в неё похожа на документоориентированные NoSQL базы данных. То есть без схемы, без таблиц. Просто
Redis используется для кэширования и как брокер для передачи сообщений. Имеется подписка на изменения и время жизни записей.
Вот пример кода:
Хотя подождите ка, всё уже придумано до нас! И это проект PyRSMQ
Что он делает?
Создаёт очередь сообщений которые может забирать другой клиент.
Как это организовать?
Для начала поднимаем cервер Redis.
Потом на одном хосте создаём очередь для отправки.
▫️Обязательное условие — наличие поднятого Redis-сервера.
▫️Единственное ограничение, обусловленное спецификой инструментов, данные должны быть JSON serializable.
#libs
По моему не очень production-ready.
Что же делать? Отправлять disckcache на сетевой диск? Не, я бы не стал. Ведь есть отличная альтернатива! Это Redis.
Redis это жутко быстрая in-memory база данных. Запись в неё похожа на документоориентированные NoSQL базы данных. То есть без схемы, без таблиц. Просто
ключ=значение.Redis используется для кэширования и как брокер для передачи сообщений. Имеется подписка на изменения и время жизни записей.
Вот пример кода:
import redis
R = redis.Redis()
R.set('key', 'value')
R.get('key')
# b'value'
Имея такой функционал, давайте реализуем что-то очень удобное для обмена данными по сети... Хотя подождите ка, всё уже придумано до нас! И это проект PyRSMQ
Что он делает?
Создаёт очередь сообщений которые может забирать другой клиент.
Как это организовать?
Для начала поднимаем cервер Redis.
Потом на одном хосте создаём очередь для отправки.
queue = RedisSMQ(На другом хосте содаём клиента для прослушивания очереди
host=host,
port=port,
qname='example'
)
queue.exceptions(False).\
createQueue(delay=0).\
vt(message_live_time).\
execute()
on_receive = lambda msg: print(msg)Начинаем прослушивание очереди
consumer = RedisSMQConsumer(
'example', on_receive
host=host, port=port)
consumer.run()Теперь можем отправлять сообщения
queue.sendMessage(delay=0).message(msg).execute()Теперь все сообщения, отправленные в очередь, будут попадать в наш колбек
on_receive.
▫️Обязательное условие — наличие поднятого Redis-сервера.
▫️Единственное ограничение, обусловленное спецификой инструментов, данные должны быть JSON serializable.
#libs
Wikipedia
Документоориентированная СУБД
Документоориентированная СУБД (англ. document-oriented database) — СУБД, специально предназначенная для хранения иерархических структур данных (документов) и обычно реализуемая с помощью подхода NoSQL. В основе документоориентированных СУБД лежат документные…
Наверняка у многих возникло желание потестить отправку сообщений, но Redis не установлен и вообще непонятно как с ним быть.
На самом деле запустить его очень просто.
🔸 Для Windows, качаем архив, запускаем redis-server.exe
🔸 Для Linux несколько команд
🔸 Если есть Docker, то еще проще
______________________
А еще с сервером можно поиграть в пинг-понг
На самом деле запустить его очень просто.
🔸 Для Windows, качаем архив, запускаем redis-server.exe
🔸 Для Linux несколько команд
🔸 Если есть Docker, то еще проще
docker run --rm -p 6379:6379 redisВсё, можно экспериментировать!
______________________
А еще с сервером можно поиграть в пинг-понг
> redis-cli ping#tricks #libs #linux
PONG
В стандартном модуле random есть две очень похожие функции
Дело в том что у
Но есть указать еще и step то наш диапазон усложняется, то есть в него попадёт не полный ряд значений.
Например, я хочу получить случайное значение из диапазона но только чётное число. Тогда достаточно сделать так:
Еще одно важное отличие в том, что
#tricks #basic
random.randint()Обе возвращают случайное значение из указанного диапазона
random.randrange()
>>> random.randint(10, 20)В чем же отличие?
12
>>> random.randrange(10, 20)
17
Дело в том что у
randrange() есть третий параметр step.randint() действительно возвращает случайное число из указанного диапазона.randrange() на первый взгляд делает тоже самое если передать также два параметра. Но есть указать еще и step то наш диапазон усложняется, то есть в него попадёт не полный ряд значений.
Например, я хочу получить случайное значение из диапазона но только чётное число. Тогда достаточно сделать так:
>>> randrange(10, 20, 2)Таким образом получается что
16
randint это частный случай randrange без указания параметра step.Еще одно важное отличие в том, что
randint() включает в диапазон второе значение а randrange() нет. То есть выражение randrange(10, 20) никогда не вернёт 20, а randint(10, 20) вернёт.#tricks #basic
Модуль objgraph позволяет нарисовать граф объектов со связями между ними.
По такому визуальному представлению иногда удобно дебажить архитектуру вашего кода. Особенно когда построение структуры происходит динамически.
Не ставьте слишком большую глубину прорисовки, я как-то поставил 50 и ждал пол часа пока движок отрисует мне 3к нод.
Вряд ли в таком графе можно что-то разобрать. К тому же рендер падает с ошибкой
И просит уменьшить скейл до 0.2, а значит даже текста не разобрать.
Так что глубина 3-5 вполне достаточно.
#libs
По такому визуальному представлению иногда удобно дебажить архитектуру вашего кода. Особенно когда построение структуры происходит динамически.
Не ставьте слишком большую глубину прорисовки, я как-то поставил 50 и ждал пол часа пока движок отрисует мне 3к нод.
Вряд ли в таком графе можно что-то разобрать. К тому же рендер падает с ошибкой
dot: graph is too large for cairo-renderer bitmaps. И просит уменьшить скейл до 0.2, а значит даже текста не разобрать.
Так что глубина 3-5 вполне достаточно.
#libs
А вы ждёте выхода Python 4? Ну зря ждёте😭
По словам Гвидо, ему хватило проблем с переходом со 2го на 3й)
Лучше постепенно развивать имеющийся функционал с полной совместимостью кодовой базы чем делать такие резкие изменения.
Велика вероятность что выше 3 мажорная версия более не поднимется.
А как же обещания про невероятные ускорения в Python 4? Очевидно, что теперь они все будут добавляться в 3ю ветку.
Вот здесь можно почитать про планы ускорения
где Гвидо обещает скорость 2х уже в 3.11 и х5 через 4 года!
Здесь можно посмотреть следующие шаги по оптимизации.
#offtop #2to3
По словам Гвидо, ему хватило проблем с переходом со 2го на 3й)
Лучше постепенно развивать имеющийся функционал с полной совместимостью кодовой базы чем делать такие резкие изменения.
Велика вероятность что выше 3 мажорная версия более не поднимется.
А как же обещания про невероятные ускорения в Python 4? Очевидно, что теперь они все будут добавляться в 3ю ветку.
Вот здесь можно почитать про планы ускорения
где Гвидо обещает скорость 2х уже в 3.11 и х5 через 4 года!
Здесь можно посмотреть следующие шаги по оптимизации.
#offtop #2to3
TechRepublic
Programming languages: Why Python 4.0 might never arrive, according to its creator
In a Q&A, Python programming language creator Guido van Rossum said it was "almost taboo to talk about a Python 4 in a serious sense" following the troubled migration from Python 2.0 to Python 3.0.
У Python есть очень удобная штука - стек вызовов.
Благодаря нему модуль traceback может показать где и что сломалось в момент выброса исключений.
Но иногда Python просто падает без какой-либо информации. Лично меня это регулярно настигает на Windows и очень бесит. Совершенно не понятно что где произошло и как искать причину. Ставить сишные дебаггеры?
Чтобы быстро понять в какой строке ошибка иногда достаточно использовать встроенный модуль faulthandler
Базовое использование очень простое:
Попробуйте уронить Python без faulthandler и с ним.
#libs #tricks
Благодаря нему модуль traceback может показать где и что сломалось в момент выброса исключений.
Но иногда Python просто падает без какой-либо информации. Лично меня это регулярно настигает на Windows и очень бесит. Совершенно не понятно что где произошло и как искать причину. Ставить сишные дебаггеры?
Чтобы быстро понять в какой строке ошибка иногда достаточно использовать встроенный модуль faulthandler
Базовое использование очень простое:
python.exe -q -X faulthandler main.pyЕсли интерпретатор упадёт, то вы хотя бы узнаете какая строчка привела к такому событию.
Попробуйте уронить Python без faulthandler и с ним.
#libs #tricks
Сегодня на Github закрывается доступ к git-операциям по паролю. Теперь доступ только по токену.
Для входа на сайт следует настроить двухфакторку.
#offtop
Для входа на сайт следует настроить двухфакторку.
#offtop
The GitHub Blog
Git password authentication is shutting down - GitHub Changelog
As previously announced, starting on August 13, 2021, at 09:00 PST, we will no longer accept account passwords when authenticating Git operations on GitHub.com. Instead, token-based authentication (for example, personal…
Как получить значение прав доступа к файлу в виде привычного формата записи 755 или 644?
В целом, способ вот такой:
До того как мы сделали split() строка была следующего вида:
Для этого последние 3 значения ставим максимальными, остальные нулевыми.
Остаётся преобразовать в восьмеричное представление и убрать префикс
Хранить и использовать их удобно в виде восьмеричного int
#tricks
В целом, способ вот такой:
>>> path = '...'Теперь разберёмся что всё это значит.
>>> print(oct(os.stat(path).st_mode & 0o777).split('o')[-1])
'755'
До того как мы сделали split() строка была следующего вида:
0o755
Что это за тип данных? Это тип int в восмиричной системе исчисления. Для преобразования в такой формат есть builtin функция oct()>>> oct(493)Как видите, результат возвращается виде строки. Но это не мешает нам создавать переменные синтаксисом восьмеричных чисел, то есть с префиксом 0o. Хотя, при распечатке мы всё равно получим int.
'0o755'
>>> x = 0o777Преобразовать эту строку в int можно функцией int(), указав базис 8
>>> print(x)
511
>>> int('0o755', 8)
493
Теперь посмотрим что нам возвращает os.stat>>> perm = os.stat(path).st_modeПреобразуем в oct
33261
>>> oct(perm)Уже почти то что надо. Чтобы оставить только нужное, отрезаем лишнее с помощью оператора & (AND).
'0o100755'
Для этого последние 3 значения ставим максимальными, остальные нулевыми.
>>> perm & 0o777Оператор работает с бинарным представлением чисел, то есть операция была вот такая:
493
0b100000111101101Вспоминаем побитовые операторы
& 0b111111111
= 0b111101101
Остаётся преобразовать в восьмеричное представление и убрать префикс
>>> oct(493)Именно в таком виде пермишены файла в коде обычно не используются.
'0o755'
>>> oct(493).split('o')[-1]
'755'
Хранить и использовать их удобно в виде восьмеричного int
os.chmod(path, 0o755)или строки
os.chmod(path, int('0o755', 8))
А зачем в строке если достаточно в int? Наприрмер чтобы в json была удобочитаемая запись. Так как сериализатор запишет восьмеричное число как обычный десятичный int>>> json.dumps(0o755)Поэтому для удобства пишем его строкой а потом преобразуем в восьмеричный int.
'493'
#tricks
Что-то вы гоните насчет "привычного вида формата 755 и 644". Я вот вообще не понял что это! 😳
Действительно, что означают цифры которые мы получили в прошлом посте?
Это кодировка, заключающая в себе режимы доступа к файлу.
Подробней можно почитать в статье про chmod.
Там можно увидеть альтернативное обозначение того же самого с помощью символов
Чтобы преобразовать восьмеричное число в такое обозначение в Python есть готовая функция
А что за знак вопроса в начале?
Давайте передадим в эту функцию необрезанное значение от os.stat
Первый символ обозначает тип объекта. Это может быть файл (
Вот простая схема данной кодировки
Если вы попробуете получить пермишены для симлинка то получите пермишены для файла
Действительно, что означают цифры которые мы получили в прошлом посте?
Это кодировка, заключающая в себе режимы доступа к файлу.
Подробней можно почитать в статье про chmod.
Там можно увидеть альтернативное обозначение того же самого с помощью символов
r w x, что значит чтение, запись, исполнение.Чтобы преобразовать восьмеричное число в такое обозначение в Python есть готовая функция
>>> stat.filemode(0o755)
'?rwxr-xr-x'
Мы видим 3 группы по 3 символа, дающие 3 типа доступа для 3 типов юзеров.А что за знак вопроса в начале?
Давайте передадим в эту функцию необрезанное значение от os.stat
>>> stat.filemode(os.stat(path).st_mode)
'drwxr-xr-x'
Это данные, которые мы безжалостно обрезали в прошлый раз😼Первый символ обозначает тип объекта. Это может быть файл (
-), директория (d) или симлинк (l).Вот простая схема данной кодировки
[1][3][3][3]
│ │ │ │
│ │ │ └──> Others Permissions
│ │ └─────> Group Permissions
│ └────────> Owner Permissions
└───────────> File Type
(разверните экран если вы с телефона)Если вы попробуете получить пермишены для симлинка то получите пермишены для файла
>>> path = '.venv/bin/python3'
>>> stat.filemode(os.stat(path).st_mode)
'-rwxr-xr-x'
Чтобы получить свойства именно симлинка, нужно это явно указать>>> stat.filemode(os.stat(path, follow_symlinks=False).st_mode)
'lrwxrwxrwx'
#tricks #basicКакой тип данных выбрать для оптимального хранения информации? Имеется в виду объем занимаемой памяти.
Можем создать 4 разных варианта объектов и сравнить сколько они занимают оперативки.
Будем создавать простой класс, класс со слотами, именованный кортеж и словарь.
Дело в том, что функция sys.getsizeof() показывает не совсем то что мы ожидаем.
Она берет результат метода
А еще он не гарантирует точность размера типов для third-party расширений.
Для точного измерения размера лучше использовать модуль pympler. Помимо данных он считает сколько места занимает вся структура классов и другая обвязка объекта.
А словарь в этом тесте оказался самый расточительный.
#tricks #libs
Можем создать 4 разных варианта объектов и сравнить сколько они занимают оперативки.
Будем создавать простой класс, класс со слотами, именованный кортеж и словарь.
from collections import namedtuple
from sys import getsizeof
NT = namedtuple('NT', 'v1 v2 v3')
class CLASS:
def init(self, x1, x2, x3):
self.v1 = x1
self.v2 = x2
self.v3 = x3
class SLOTS:
slots = ['x1', 'x2', 'x3']
def init(self, x1, x2, x3):
self.x1 = x1
self.x2 = x2
self.x3 = x3
d = dict(x1=1, x2=2, x3=3)
c = CLASS(1, 2, 3)
s = SLOTS(1, 2, 3)
t = NT(1, 2, 3)
Теперь распечатаем что там по памятиprint(' CLS\t\tSLT\t\tDCT\t\tTPL')
print(f'System: {getsizeof(c)}\t\t'
f'{getsizeof(s)}\t\t'
f'{getsizeof(d)}\t\t'
f'{getsizeof(t)}'
)
CLS SLT DCT TPL
System: 48 56 232 64
Хм, в этой статистике обычный класс самый экономный! Но что-то здесь не так. Неужели он экономичней класса со слотами, который рассчитан на скорость и оптимизацию?Дело в том, что функция sys.getsizeof() показывает не совсем то что мы ожидаем.
Она берет результат метода
__sizeof__ у объекта и добавляет кое-чего от gc.__sizeof__ возвращает размер, занимаемый данными. Но не учитывает размер обвязки этих данных.А еще он не гарантирует точность размера типов для third-party расширений.
Для точного измерения размера лучше использовать модуль pympler. Помимо данных он считает сколько места занимает вся структура классов и другая обвязка объекта.
from pympler import asizeof
print(f'Pympler: {asizeof.asizeof(c)}\t\t'
f'{asizeof.asizeof(s)}\t\t'
f'{asizeof.asizeof(d)}\t\t'
f'{asizeof.asizeof(t)}'
)
CLS SLT DCT TPL
Pympler: 416 152 496 160
И вот тут класс со слотами оказывается самым оптимальным решением! И это правильно. А словарь в этом тесте оказался самый расточительный.
#tricks #libs
Наверняка вы знаете что такое перегруженная функция в С++. Если нет, то всё просто.
В С++ можно создать несколько функций с одинаковым названием но разными типами аргументов. И это будут разные функции.
Во время вызова функции будет выбрана та её версия которая подходит по типам аргументов. Такая конструкция называется параметрический полиморфизм.
Это удобно, когда мы точно не знаем какого типа прилетит аргумент и хотим обработать разные ситуации.
Как мы это поведение можем повторить в Python? Обычно через проверку типов.
Начиная с версии 3.4 в Python добавили способ делать "перегруженные" функции более элегантно. Это декоратор singledispatch.
Создадим нашу исходную функцию которая по умолчанию выбрасывает ошибку.
В качестве аргумента указывайте тип который данная функция обрабатывает
Имя новой функции не имеет значения, часто её называют просто "_"
◽️ Данный способ работает только с первым аргументом. Все остальные аргументы будут переданы как есть и не участвуют в выборе нужной функции.
◽️ В версии 3.8 доступен декоратор
#tricks #libs
В С++ можно создать несколько функций с одинаковым названием но разными типами аргументов. И это будут разные функции.
Во время вызова функции будет выбрана та её версия которая подходит по типам аргументов. Такая конструкция называется параметрический полиморфизм.
Это удобно, когда мы точно не знаем какого типа прилетит аргумент и хотим обработать разные ситуации.
Как мы это поведение можем повторить в Python? Обычно через проверку типов.
def func(value):Не очень красиво 🧐
if isinstance(value, int):
return func_int(value)
elif isinstance(value, str):
return func_str(value)
else:
raise NotImplementedError
Начиная с версии 3.4 в Python добавили способ делать "перегруженные" функции более элегантно. Это декоратор singledispatch.
Создадим нашу исходную функцию которая по умолчанию выбрасывает ошибку.
@singledispatchДекоратор добавил для объекта
def func(value)
raise NotImplementedError
func новую функцию register() с помощью которой можем регистрировать перегруженные функции.В качестве аргумента указывайте тип который данная функция обрабатывает
@func.register(int)Вместо указания типа в аргументах можно использовать аннотации аргумента функции
def func_int(x):
print("INT:", x)
@func.registerЕсли не указать тип одним из этих способов то получите ошибку TypeError.
def func_str(x: str):
print("STR:", x)
Имя новой функции не имеет значения, часто её называют просто "_"
@func.register(list)Если одна функция должна обработать несколько типов, то просто наслаиваем декоратор
def _(x):
print("LIST", x)
@func.register(float)Теперь у нас есть 5 отдельных функций которые вызываются в зависимости от типа передаваемого аргумента. При этом всё выглядит логично и компактно. Обработка каждого случая находится в своей отдельной функции!
@func.register(Decimal)
def _(x):
print(f'{type(x).__name__.upper()}:', x)
>>> func(1)------
INT: 1
>>> func('Python')
STR: Python
>>> func(1.2)
FLOAT: 1.2
>>> func({})
NotImplementedError
◽️ Данный способ работает только с первым аргументом. Все остальные аргументы будут переданы как есть и не участвуют в выборе нужной функции.
◽️ В версии 3.8 доступен декоратор
singledispatchmethod с таким же функционалом но для методов класса.#tricks #libs
В одном из прошлых постов был вопрос в комментариях по поводу закрытия файла.
Тогда я бегло пояснил что Python сам собирает мусор в памяти.
Давайте пройдемся по этому вопросу более внимательно.
Когда вы открываете файл не сохраняя его в переменную, на самом деле файл остаётся открытым. Ссылка на него теряется где-то в памяти пока сборщик мусора не доберется до него.
Давайте сделаем несколько экспериментов. Откроем файл не сохраняя его в переменную и поищем этот объект в памяти.
Для поиска будем использовать вот такую функцию
Тест второй: теперь откроем файл просто в коде, можно в интерактивной консоли.
_________________
Мои примеры порой содержат оптимизации в угоду краткости но в ущерб правильности.
#tricks
Тогда я бегло пояснил что Python сам собирает мусор в памяти.
Давайте пройдемся по этому вопросу более внимательно.
Когда вы открываете файл не сохраняя его в переменную, на самом деле файл остаётся открытым. Ссылка на него теряется где-то в памяти пока сборщик мусора не доберется до него.
Давайте сделаем несколько экспериментов. Откроем файл не сохраняя его в переменную и поищем этот объект в памяти.
Для поиска будем использовать вот такую функцию
def check_file():Тест первый: файл, открытый в функции
import io, gc
for obj in gc.get_objects():
if isinstance(obj, io.TextIOWrapper) and \
obj.name == 'testfile':
print('File', obj.name, 'is closed:', obj.closed)
return
print('Not found')
def func():Вызываем функцию и проверяем наличие файла
open('testfile', 'w')
>>> func()Здесь всё предсказуемо. Все локальные переменные функции удаляются когда функция завершится. А при удалении файл закрывается.
>>> check_file()
Not found
Тест второй: теперь откроем файл просто в коде, можно в интерактивной консоли.
open('testfile', 'w')
И снова он не сохранён в переменную, а значит будет удалён сборщиком мусора. Проверим:>>> check_file()Бывалые сразу же скажут, что файл улетел в переменную "_", и будут правы!
File testfile is closed: False
>>> print(_)Давайте обнулим её, можно просто распечатать любое число в консоли
<_io.TextIOWrapper name='testfile' mode='w' encoding='UTF-8'>
>>> 123Проверим
>>> print(_)
123
>>> check_file()➡️ Отсюда вывод: Всегда явно закрывайте файлы! Не рассчитывайте что кто-то (gc) сделает это за вас.
Not found
_________________
Мои примеры порой содержат оптимизации в угоду краткости но в ущерб правильности.
#tricks
В Python есть удобный режим, определяющий код с неверно закрытыми ресурсами. Этот режим называется Development Mode и включается двумя способами:
Переменная окружения
Вот пример файла
При этом ResourceWarning всё равно будет выброшен, причём еще до использования psutil.
➡️ Отсюда вывод: Всегда явно закрывайте файлы! Пишите чистый и предсказуемый код.
#tricks #libs
Переменная окружения
export PYTHONDEVMODE=1Аргументы
python3 app.py
python3 -X dev app.pyЕсли не закрыть файл должным образом, то вы получите в консоль ResourceWarning.
Вот пример файла
# app.pyВ этом примере я использую пакет psutil, чтобы убедиться, что перед выходом открытых файлов в моём процессе не осталось.
import psutil, os
open('testfile', 'w')
123
print('process handlers:', psutil.Process(os.getpid()).open_files())
При этом ResourceWarning всё равно будет выброшен, причём еще до использования psutil.
app.py:3: ResourceWarning: unclosed file <_io.TextIOWrapper name='testfile' mode='w' encoding='UTF-8'>Для отображения строки с ошибкой требуется включить tracemalloc, тоже с помощью переменой или аргументов запуска. Смотрите примеры в доке.
open('testfile', 'w')
Object allocated at (most recent call last):
File "app.py", lineno 3
open('testfile', 'w')
process handlers: []
➡️ Отсюда вывод: Всегда явно закрывайте файлы! Пишите чистый и предсказуемый код.
#tricks #libs
👍1
Особенно внимательно за закрытием файлов нужно следить в задачах где вы обрабатываете много файлов.
Операционная система имеет ограничение на количество открытых файлов процессом и вы быстро можете дойти до этого предела.
Изменить лимит можно командой
#tricks
Операционная система имеет ограничение на количество открытых файлов процессом и вы быстро можете дойти до этого предела.
import osЧтобы узнать лимит на Linux вызовите команду
lst = []
for i in range(100000):
lst.append(open(os.devnull, 'w'))
OSError: [Errno 24] Too many open files: 'nul'
ulimit -n(Полный список лимитов
ulimit -a)Изменить лимит можно командой
ulimit -n 2048На Windows можно это сделать с помощью кода
>>> import ctypes➡️ Отсюда вывод: Всегда явно закрывайте файлы! Иначе можете упереться в ограничения системы.
>>> ctypes.windll.msvcrt._getmaxstdio()
512
>>> ctypes.windll.msvcrt._setmaxstdio(2048)
2048
#tricks
Python 3.10 Release Stream — with Pablo Galindo
Стрим, посвящённый релизу Python 3.10!
➡️ https://www.youtube.com/watch?v=AHT2l3hcIJg
Стрим, посвящённый релизу Python 3.10!
➡️ https://www.youtube.com/watch?v=AHT2l3hcIJg