TGINSIGHT CHAT
Python Заметки
@pythonotes
EducationИнтересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop
Неодамнешни објави
Ознака: #libs · 124 објави
Објавено 17 јун.
Как сохранить картинку непосредственно в Python-модуль? Для этого нам пригодится библиотека base64. Этот способ кодирование позволяет любые бинарные или текстовые данные закодировать с помощью 64 ASCII символов. То есть получится простая строка. Зачем это вообще? Это обратимое кодирование позволит любые бинарные данные сохранить в текстовом виде и отправить туда где для записи поддерживается только текст. Например: - встроить в URL в GET запрос как параметр - встроить в тело email - сохранить в Python-модуль как переменную - сохранить любой конфиг, например JSON - записать в базу данных - зашить в HTML (XML) или CSS Чаще всего так кодируют изображения в HTML и в CSS. Есть даже специальные сервисы для кодирование изображений. Давайте закодируем и декодируем картинку. Кодирование: >>> import base64 >>> src_path = 'image1.png' >>> with open(src_path, 'rb') as f: >>> raw_data = f.read() >>> image_encoded = base64.encodebytes(raw_data) >>> print(image_encoded) b'iVBORw...Jggg==\n' Теперь наша картинка это просто байты в переменной. Её можно сохранить непосредственно в модуле и использовать позже. Декодирование: >>> save_path = "image2.png" >>> raw_data = base64.decodebytes(image_encoded) >>> with open(save_path, 'wb') as f: >>> f.write(raw_data) Картинка восстановлена обратно в файл. Стоит помнить что: - это не шифрование, пароли так не стоит прятать. Строка легко декодируется в исходник. - размер данных после кодирования увеличивается примерно на четверть - не храните изображения в базе данных таким способом! #libs#tricks
Објавено 15 јун.
В стандартной библиотеке os есть интересный метод os.nice(). Как написано в документации: Add increment to the process’s "niceness". Добавляет "любезности" процессу??? Ну почти... В Unix системах есть стандартная утилита nice, которая может контролировать приоритет использования CPU процессом. Обычно это значение 0, что значит стандартный приоритет. Но если добавить "любезности", то процесс будет больше отдавать другим, оставляя себе минимальный приоритет. Или наоборот. Запустим тест чтобы визуально увидеть разницу. 🔸 Загрузите процессор на 100%. Можно поставить любую тяжёлую задачу. Например, сохраните этот код в файл и запустите в консоли (Python3) 🔸Напишите функцию которая активно использует процессор и считает потраченное время. import time def compute(): array = [] for i in range(10): start = time.perf_counter() for i in range(1000000): x = 2*2 array.append(time.perf_counter()-start) return sum(array) / len(array) Моя функция запускает 10 раз некий код и возвращает среднее время. 🔸Тестируем import os # запускаем первый тест в дефолтным приоритетом t1 = compute() # меняем приоритет на минимальный os.nice(19) # запускаем второй тест t2 = compute() # смотрим реузльтат print("Test 1", t1, "sec") print("Test 2", t2, "sec") print("Diff", t2/t1, "times") В консоль распечатается время каждого теста и разница времени. 🌎 Код тестов 🔸 Где может пригодиться? - Повышение приоритета позволит захватывать больше процессорного времени, выполняя важные задачи быстрей. - Понижение приоритета позволит повысить отзывчивость компьютера во время выполнения тяжёлых расчётов. 🔸Что еще нужно помнить? - Чтобы добавить приоритет нужно в функцию os.nice() отправить отрицательное значение. Так как данный метод всегда прибавляет число к текущему приоритету. - Понижение приоритета доступно любому юзеру, повышение доступно только если процесс запущен от суперюзера. - Узнать текущее значение: os.nice(0) - Запускаемые подпроцессы наследуют приоритет. - работает только на Linux. - Доступный диапазон значений -20...19. #libs
Hashtags
Објавено 5 јун.
Бывают задачи когда из большого массива объектов требуется отфильтровать эти объекты по категориям. В результате получаем словарь примерно такого вида: data = { "category1": [item1, item2, ...], "category2": [item1, item2, ...], ... } При этом заранее мы не знаем список категорий и их необходимо добавлять в процессе итерации. Как такой код будет выглядеть: items = [...] sorted_items = {} for item in items: cat = get_category(item) if cat not in sorted_items: sorted_items[cat] = [] sorted_items[cat].append(item) Значением ключа является не сам элемент а промежуточный объект это список. Потому нам следует сначала убедиться, что он есть, а если нету то создать. Альтернативный код делающий тоже самое: for item in items: cat = get_category(item) if cat not in sorted_items: sorted_items[cat] = [item] else: sorted_items[cat].append(item) Чтобы избежать этой проверки можно использовать тип defaultdict, это очень простой класс который сам за вас сделает проверку и добавит нужный тип если его нет. Просто укажите нужный тип в конструкторе. from collections import defauldict items = [...] sorted_items = defaultdict(list) for item in items: sorted_items[get_category(item)].append(item) Если указанного ключа нет в словаре, то он создаётся сразу со списком в значении и возвращается как будто он там и был. #tricks#libs
Објавено 3 јун.
Порой бывает необходимо работать с JSON файлами ручками, читая или изменяя данные. И очень не удобно, когда юникод в файле записан в виде кодированных символов. >>> import json >>> data = {'title': 'Привет Медвед!'} >>> print(json.dumps(data)) '{"title": "\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442 \\u041c\\u0435\\u0434\\u0432\\u0435\\u0434!"}' Эх, безобразие! Ни прочитать нормально, ни поправить. Чтобы такое поведение изменить, достаточно добавить аргумент ensure_ascii=False >>> json.dumps(data, ensure_ascii=False) '{"title": "Привет Медвед!"}' Теперь символы не кодируются в Unicode. В файл запишется в таком же виде. ____________________ Для тех кто в танке (всё еще на Python 2🚂 ). Строку следует делать как unicode, и для записи в файл использовать модуль codecs. >>> import json, codecs >>> data = {'title': u'Привет Медвед!'} >>> with codecs.open(path, "w", encoding='utf-8') as f: >>> json.dump(data, f, ensure_ascii=False) #libs#tricks
Објавено 1 јун.
Часто используете Python в терминале? Скорее всего вас не особо устраивает дефолтный REPL. Советую попробовать прокаченные версии интерактивного шела: 🔸bpython - подсветка синтаксиса - автокомплиты - инлайн подсказки параметров функций - история команд - авто отступы Установка: pip3 install bpython Сайт 🌎 https://bpython-interpreter.org/ 🔸ptpython - подсветка синтаксиса - автокомплиты - поддержка мышки - авто отступы - цветовые темы Установка: pip3 install ptpython Сайт 🌎 https://github.com/prompt-toolkit/ptpython Также можно глянуть: ➡️www.asmeurer.com/mypython ➡️xon.sh ➡️ipython.org #libs
Hashtags
Објавено 29 мај
В стандартной поставке Python есть один полезный инструмент в библиотеке collections, это класс deque. он очень похож на простой список но он намного быстрее работает в некоторых случаях. Например для обработки элементов в начале списка у него есть дополнительные методы: extendleft(), appendleft() и popleft(). Запустим пару тестов!!! 🚀 >>> from collections import deque >>> import time >>> >>> st = time.perf_counter() >>> for _ in range(1000): >>> l1 = deque() >>> for i in range(5000): >>> l1.append(i) >>> en = round(time.perf_counter()-st, 3) >>> print(f'Test deque: {en}sec') >>> >>> st = time.perf_counter() >>> for _ in range(1000): >>> l2 = list() >>> for i in range(5000): >>> l2.append(i) >>> en = round(time.perf_counter()-st, 3) >>> print(f'Test list: {en}sec') Test deque: 0.452sec Test list: 0.436sec Добавление в конец списка работает примерно одинаково. Теперь попробуем вставлять элемент в начало массива. >>> st = time.perf_counter() >>> for _ in range(1000): >>> l1 = deque() >>> for i in range(5000): >>> l1.appendleft(i) >>> en = round(time.perf_counter()-st, 3) >>> print(f'Test deque: {en}sec') >>> >>> st = time.perf_counter() >>> for _ in range(1000): >>> l2 = list() >>> for i in range(5000): >>> l2.insert(0, i) >>> en = round(time.perf_counter()-st, 3) >>> print(f'Test list: {en}sec') Test deque: 0.435sec Test list: 6.347sec Прирост производительности почти в 15 раз! 😲 Тестим функцию pop() (опустим код теста для краткости) Test deque: 0.48sec Test list: 0.529sec Теперь pop(0) для list и popleft() для deque Test deque: 0.476sec Test list: 3.101sec Быстрей примерно в 6.5 раз. Почему так быстро? Дело в том что deque это некий аналог такого типа данных как "linked list data structure", в Python это занывается "двусвязные списки" (doubly-linked lists). Это список, но не в привычном представлении, а с особой оптимизированной структурой. При создании такого массива данные никуда не переносятся а только линкуются оттуда где были. Да, это похоже на Python-лист, но линковка происходит иначе. Вместо того чтобы собирать некий стек ссылок и назвать его списком, в doubly-linked list каждый элемент просто ссылается на следующий. Такая структура позволяет значительно ускорить, создание списка, изменение с любой стороны. Где это может пригодиться? Конечно же в двусторонних очередях (double-ended queue). То есть когда мы добавляем элементы в начало а забираем с конца, или наоборот. Минус такого подхода в просадке производительности для произвольного доступа к элементам в середине очереди. Полный код тестов 🌎 ______________ Реальное описание несколько сложней, я постарался передать основную суть. #libs#tricks
Објавено 25 мај
Вы всё еще проверяете секретные данные оператором сравнения? >>> if password == user_password: >>> ... Это небезопасный способ сравнения для стендалон приложений. Он уязвим к такому типу атаки как timing attack, позволяющий делать выводы и угадывать пароль на основании времени проверки. Чтобы сделать безопасное сравнение используйте метод secrets.compare_digest(). Он защитит операцию проверки от подобных атак. >>> import secrets >>> if secrets.compare_digest(password, user_password): >>> ... Возможно вы ранее слышали про метод hmac.compare_digest(). Он не только делает то же самое, это один и тот же метод! >>> import secrets >>> import hmac >>> hmac.compare_digest is secrets.compare_digest True #libs
Hashtags
Објавено 21 мај
В Python есть стандартный модуль sched для синхронного планировщика задач. Что??? Синхронных??? В наш-то век "асинхронщины" и "параллельщины"! Спокойно, сначала смотрим код, потом разбираемся. Работает это так: - создаём планировщик - добавляем задачи в очередь с таймаутом и приоритетом - запускаем и ждём пока завершится вся очередь Смотрим пример: import sched, time def func(name): t = round(time.time()-start_time, 2) print(f"Execute {name} ({t}s)") # просто отметка времени старта start_time = time.time() # создаём планировщик s = sched.scheduler( time.time, # функция замера времени time.sleep # функция ожидания ) # добавляем задачи s.enter(0, 1, func, argument=('ev1',)) s.enter(2, 1, func, argument=('ev2',)) s.enter(1, 1, func, argument=('ev3',)) # запускаем очередь на исполнение s.run() Описание аргументов Вывод получаем в соответствии со временем задержки: Execute ev1 (0.0s) Execute ev3 (1.0s) Execute ev2 (2.0s) Функция run() запускает планировщик и начинается выполнение задач в порядке очереди по времени и приоритету. Время указывается с момента старта очереди. Если время совпадает то сортировка идёт по приоритету. Что значит синхронный планировщик? Это значит что задачи будут выполняться строго по очереди в одном потоке. Никаких мультипотоков и мультипроцессов, модуль прост как бревно! Если у первой задачи стоит задержка 0сек а у второй 1сек, и при этом первая задача выполняется 3сек, то вторая задача выполнится только через 3 сек. Никакого параллельного запуска не будет. Параметр delay следует понимать не как "запусти через N сек" а как "запусти не раньше чем через N сек". Время запуска следующей зависит от выполнения предыдущих задач. Где это может пригодиться? Очередь задач, между которыми должен быть промежуток времени по какой-либо причине. 🔸 несколько синхронных задач, которые должны выполняться друг за другом но требующие ожидания обновления какой-либо инфраструктуры (тормозная сеть?) 🔸 сетевой API который имеет лимит на количество команд в единицу времени 🔸 фейковая задержка для генерации тестов или имитация поведения юзера. Конечно, всё это можно решить банальным time.sleep() в нужном месте, но sched даёт несколько более удобный интерфейс управления задачами. #libs
Hashtags
Објавено 27 апр.
Ранее я делал серию постов про битовые операторы. Вот вам ещё один наглядный пример как это используется в Python в модуле re. Чтобы указать флаг для компилятора нам надо указать его после передаваемой строки. Например, добавляем флаг для игнорирования переноса строки. pattern = re.compile(r"(\w+)+") words = pattern.search(text, re.DOTALL) А как указать несколько флагов? Ведь явно будут ситуации когда нам потребуется больше одного. Кто читал посты по битовые операторы уже понял как. pattern.search(text, re.DOTALL | re.VERBOSE) А теперь смотрим исходники, что находится в этих атрибутах? Не удивительно, степени двойки. Почему? Потому что каждое следующее значение это сдвиг единицы влево. >>> for n in [1, 2, 4, 8, 16, 32, 64, 128, 256]: >>> print(bin(n)) 0b1 0b10 0b100 0b1000 0b10000 0b100000 0b1000000 0b10000000 0b100000000 Чтобы было понятней, давайте напишем тоже самое но иначе, добавим ведущие нули: 000000001 000000010 000000100 000001000 000010000 000100000 001000000 010000000 100000000 Не понятно что тут происходит? Читай три поста про битовые операторы начиная с этого ➡️https://t.me/pythonotes/45 В общем, это пример применения побитовых операций в самом Python. Теперь вы знаете Python еще немного лучше) #tricks#regex#libs
Објавено 25 апр.
Хотите оформить свои CLI скрипты красивым прогрессбаром? Стоит посмотреть на библиотеку progress. Она даёт возможность создавать красивые прогрессбары легко и быстро. Но что если не хочется добавлять лишнюю зависимость только ради одной бегущей полоски? Создать свою функцию с подобным функционалом — не проблема! Вот базовый набросок: import time for i in range(0, 100+1, 4): time.sleep(0.1) print('\r{:>3}% [{}{}]'.format(i, "#"*i, '-'*(100-i)), end='') print() Запустите код в интерактиве и увидите что работает не хуже. Вся хитрость в отсутствии переноса на новую строку в конце строки и в символе "\r" — возврат "каретки" (читай "курсора") в начало строки. После чего мы перезаписываем предыдущую строку. Главное в самом конце не забыть обычный print() чтобы перейти на новую строку. Расширенный вариант в виде контекст менеджера. 🌎 #libs#tricks
Објавено 19 апр.
Стандартный модуль json имеет command line интерфейс. Он умеет делать валидацию JSON-данных и переводить однострочный вариант в форматированный. Изменяем форматирование $ echo '{"key1": "value1", "key2": "value2"}' | python3 -m json.tool { "key1": "value1", "key2": "value2" } Заменяем прямой ввод данных на данные из файлов python3 -m json.tool < single.json > pretty.json Вместо stdin и stdout просто передаём путь к файлам, результат тот же. python3 -m json.tool single.json pretty.json Как происходит валидация? Да просто команда завершится с ошибкой если формат данных неверный (exit code 1). В stderr распечатается информация о том где произошёл сбой. #libs
Hashtags
Објавено 25 мар.
Почему форматов для хранения простых конфигов так много? Всё как обычно. В какой-то момент возникает ситуация: АДМИН: Этот конфиг просто не поддерживает то что я от него хочу! ПРОГЕР: Подержите моё пиво... И рождается новый формат))) А если серьёзно, разные форматы поддерживают разный функционал. Например, поддержка группировки, вложенности данных, глубина вложенности, поддержка разных структур данных, поддержка экспрешенов и наследования, удобство расширения и тд. Возможно самое главное, это человекочитаемость! Так как очень часто конфиги пишут и читают люди. Если бы это было не так, то все писали бы в binary и не выдумывали всякое хитрое форматирование. На мой взгляд самый удобный в коде это JSON, а самый читаемый и удобный в ручном редактировании это YAML. Никто не мешает вам создать собственный формат, если не достаточно того что имеется или если слишком много свободного времени) PS. 4 формата рядом для сравнения синтаксиса 🌎 #libs
Hashtags