Python Заметки
2.31K subscribers
58 photos
2 videos
2 files
212 links
Интересные заметки и обучающие материалы по Python

Контакт: @paulwinex

⚠️ Рекламу на канале не делаю!⚠️

Хештеги для поиска:
#tricks
#libs
#pep
#basic
#regex
#qt
#django
#2to3
#source
#offtop
Download Telegram
Startup скрипт для REPL.

Как выполнить скрипт сразу после старта интерактивной консоли Python?
Для начала понять бы зачем это может понадобиться. А причины бывают достаточно весомые

- автоматизировать одни и те же действия которые вы повторяете при старте REPL
- кастомизировать сам REPL
- объявление энвайромента или констант для ручного дебага или тестирования
итд...

В общем, как-то ускорить свою работу с REPL.

Как пример поведения, команда shell_plus в пакете django-extensions, которая перед запуском шела импортит всё самое необходимое.

Есть два способа это сделать

🔸 Флаг i
Запишите все действия в скрипт, например startup.py, и запускайте консоль такой командой:

python -i startup.py

Флаг -i означает, что после выполнения скрипта инетрпретатор не завершит процесс а перейдёт в интерактивный режим, то есть обычный RELP.

🔸 Переменная окружения PYTHONSTARTUP
Еще до старта самого Python объявляем переменную PYTHONSTARTUP, в которую следует указать путь к скрипту. Сразу после запуска REPL но до первого приглашения к вводу команды ваш скрипт исполнится.

Windows
set PYTHONSTARTUP=C:\path\to\startup.py

Linux
export PYTHONSTARTUP=/path/to/startup.py

Теперь запуск REPL будет исполнять указанный файл

Всё это относится только к интерактивной консоли REPL!

Если вам нужен аналогичный функционал для обычного запуска приложения, то его нет. Так как это легко реализовать штатными средствами, добавив нужные импорты в начало вашего приложения.

#tricks
Выполнение функции перед выходом.

Как вызвать функцию перед выходом из программы в Python?
Для этого нужно использовать модуль atexit. Он регистрирует функции, которые выполняются перед завершением процесса интерпретатора.

import atexit

def before_exit():
print('BEFORE EXIT')

atexit.register(before_exit)

Теперь попробуйте завершить процесс и увидите сообщение

>>> exit()
BEFORE EXIT

🔸 Для регистрации такой функции в REPL пригодится startup скрипт.
🔸 Этот функционал работает и в обычном режиме без интерактивной консоли.

#tricks #libs
Стартап-скрипт это удобное место для изменения внешнего вида REPL. Например замена символов строки приглашения или добавление автокомплитов по TAB.
Давайте заменим символы строки приглашения. Для этого нужно поменять переменные sys.ps1 и sys.ps2. Символами может быть даже эмодзи.

Напишите это в стартап-скрипте:

import sys
sys.ps1 = ' '
sys.ps2 = ' '

А как насчет динамически изменяемой строки? Это тоже можно. Создаём класс, наследованный от строки и определяем что он возвращает при печати.

import sys

class PS1(object):
def __init__(self):
self.s = True
def __str__(self):
self.s = not self.s
return '\033[97m█◣ \033[0m ' if self.s else '\033[34m█◤ \033[0m '

sys.ps1 = PS1()
sys.ps2 = '▼ '

Попробуйте поработать с этим)
___________
Еще пример python-startup скрипта с просторов гитхаба.
И пример от меня, рандомный смайл на каждой строке 😂

#tricks #libs
Чем отличаются эти термины?

Шифрование
Кодирование
Хеширование

🔸 Шифрование
Обратимое преобразование информации с целью скрыть её содержимое. Шифруются данные с помощью секретного ключа, без которого расшифровать данные в исходное состояние невозможно.

Примеры использования:
- Шифрование бинарных файлов и архивов с помощью пароля
- Шифрование текста

🔸 Кодирование
Это преобразование данных из одного вида в другой. При этом содержимое данных остаётся прежним, меняется лишь форма (способ представления данных). Всегда можно вернуть данные в прежний вид с помощью обратного кодирования (декодирование).

Примеры использования:
- Кодирование текста из ASCII в UTF-8
- Кодирование изображения из BMP в TIF
- Кодирование строки в байты

🔸 Хеширование
Необратимое преобразование данных в строку определённой длины. Содержимое хеш-строки строго зависит от исходных данных и сильно изменяется даже если в данных изменить 1 бит. Хеширование всегда выдаёт одинаковый результат при одинаковых исходных данных. "Дехешировать" строку обратно в данные невозможно.

Примеры использования:
- Хранение пароля в базе данных в виде хеш-строки не раскрывает пароль и оставляет возможность проверять правильность введённого пользователем пароля.
- Хеш-строку файла еще называют контрольной суммой. Позволяет быстро проверить одинаковы ли файлы сравнив лишь хеши этих файлов. Даже если один пиксель в картинке изменится, контрольная сумма будет другой. Очень полезно для проверки необходимости обновления файлов по сети или поиска файлов.

🐍 Модули

🔹 Для работы с криптографией в Python используются внешние модули

cryptography
PyCrypto

🔹Хеширование и другие манипуляции с паролями и токенами

crypt
hashlib
secrets
hmac

Информация здесь

🔹 Всё что связано с кодировками

codecs

#libs
⭐️ На этой неделе преодалён рубеж в 1К подписчкивов!
Спасибо вам, что читаете мои заметки 😍.
Это хорошая мотивация к развитию. Давайте вместе определим вектор этого развития. Как вы думаете, чего больше всего сейчас не хватает этому каналу?
Final Results
23%
💬 Чат для обсуждений и вопросов
20%
👍 Кнопки с реакциями под постом
55%
🎬 Видео уроки или видео курс
43%
📝 Блог с длинными статьями
12%
🎤 Стримы
Спасибо за ваши ответы. Думаю, результат опроса вполне очевиден.
Пойду обдумаю варианты реализации 😉
В Python2 была интересная возможность кодировать строку с помощью ZIP-архивации.
Ведь что такое кодирование? Просто преобразование данных из одного вида в другой. Компрессия это тоже просто другая форма данных.
Представление данных в виде ZIP просто и легко сокращает размер этих данных. Это бывает полезно или даже критично при передаче данных по медленным каналам.

# Python2
>>> my_str = 'Hello ZIP'
>>> my_zip_str = my_str.encode('zip')
>>> print my_zip_str
xЬєH═╔╔WИЄ♀ ☼╨♥️

Да, в результате это нереально прочитать, но мы же понимаем что это просто такая форма данных. Наша строка всё еще где-то там.
Пробуем преобразовать обратно.

>>> my_zip_str.decode('zip')
'Hello ZIP'

Всё на месте!
Пытливые умы заметили, что мы нифига не сэкономили 😭

>>> print len(my_str), len(my_zip_str)
9, 17

Увеличили размер почти в 2 раза!
Да, это так. Но вся сила ZIP раскрывается на больших данных

>>> my_str = 'Hello ZIP ' * 100
>>> my_zip_str = my_str.encode('zip')
>>> print len(my_str), len(my_zip_str)
1000 27

Чем больше строка и чем больше в ней повторов, тем лучше она сожмётся. В данном случае в 37 раз меньше данных.

Да, круто, но к чемуто это я? Дело в том что в Python3 этот код не сработает.
Во-первых, кодек переименован в "zlib_codec", во-вторых подобный код вызовет ошибку и отправит нас в модуль codecs.

my_str.encode('zlib_codec')
LookupError: 'zlib_codec' is not a text encoding; use codecs.encode() to handle arbitrary codecs

Данный функционал можно повторить и в Python3 но кода получится куда больше.
И что теперь? Неужели я предлагаю заняться "некрокодингом" на Python2? Нет, не нужно тревожить пенсионера 🚑.

Всё будет ясно вследующем посте.

#libs
Вы знаете, что строки в Python можно кодировать и декодировать в разные кодировки.

>>> s = 'Привет'
>>> s.encode() # в байты
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

>>> s.encode('ascii') # в ASCII (если получится)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

>>> s.encode('euc_jp') # японская кодировка
b'\xa7\xb1\xa7\xe2\xa7\xda\xa7\xd3\xa7\xd6\xa7\xe4'

>>> s.encode('hz') # Simplified Chinese
b"~{'1'b'Z'S'V'd~}"

И другие...

А можно ли добавить свою кодировку в этот список?
Можно! И сейчас мы это сделаем. Давайте добавим ZIP-кодировку из Python2, о которой мы говорили в прошлом посте.

Для начала нам нужен алгоритм. Я хочу сжимать строку через ZIP и после получения байтов преобразовывать их в строку через base64 (можно и без base64). Получится такой код:

import zlib, base64

>>> orig = 'hello python'
>>> compressed = zlib.compress(orig.encode(), 5)
>>> compresed_b = base64.encodebytes(compressed)
>>> print(compresed_b)
b'eF7LSM3JyVcoqCzJyM8DAB7wBNc='

Да, строка получилась длинней чем была. Но мы помним, что всё будет иначе если исходная строка большая.

Обратный алгоритм декодирования

>>> compressed = base64.decodebytes(compresed_b)
>>> restored = zlib.decompress(compressed).decode()
>>> orig == restored
True

Отлично! 😎
Теперь, чтобы создать кодек на базе этих алгоритмов, следует воспользоваться функций codecs.register().
В неё подаётся имя функции которая должна вернуть объект codecs.CodecInfo.
Данный объект будет содержать две функции - кодирование и декодирование. Это и будет наш кодек.
Давайте назовём наш кодек просто "z". Тогда его создание будет выглядеть как-то так:

import zlib, base64, codecs

def z_encode(data):
return base64.encodebytes(zlib.compress(data.encode(), 5)), 0

def z_decode(data):
return zlib.decompress(base64.decodebytes(data)).decode(), 0

def z_search(encoding_name):
return codecs.CodecInfo(z_encode, z_decode, name='z')

codecs.register(z_search)

Тестим!

>>> s = 'Hello New codec!'
>>> s_enc = s.encode('z')
>>> print(s_enc)
b'eF7zSM3JyVfwSy1XSM5PSU1WBAAvxwV+\n'

Теперь обратно

>>> s_enc.decode('z')
'Hello New codec!'

А теперь практика!. Сжимаем JSON со списком файлов

>>> from pathlib import Path
>>> import json

>>> files = list(map(str, list(Path('~/Documents').expanduser().glob('**/*'))))
>>> print(len(files))
5390
>>> text = json.dumps(files)
>>> print(len(text))
513839
>>> enc = text.encode('z')
>>> print(len(enc))
53317

Профит почти в 10 раз!
Можно еще уменьшить размер если обойтись без BASE64. Но тогда вы получите чистые байты и как строку их передать уже не получится.

PS. А вот стандартный и самый короткой способ для ZIP-сжатия байтов в Python3

>>> import codecs
>>> codecs.encode(my_bytes, 'zip'))

(спасибо @amarovita за пример кода)
#tricks
Те, кто в Python не первый день, хорошо знают, что на число можно умножить не только число, но и другие типы. Главное, чтобы у этих типов была реализация такой операции.

# list
>>> [1] * 3
[1, 1, 1]
# tuple
>>> (2, 3) * 3
(2, 3, 2, 3, 2, 3)
# string
>>> "A" * 3
"AAA"

Так работает полиморфизм стандартных типов. Интересно здесь то, что это сработает и в том случае, когда порядок операндов обратный. То есть int умножить на [тип].

# list
>>> 3 * [1]
[1, 1, 1]
# tuple
>>> 3 * (2, 3)
(2, 3, 2, 3, 2, 3)
# string
>>> 3 * "A"
"AAA"

Если хотите реализовать такое поведение в ваших классах то следует помнить два момента:

1. Если множитель справа, то вам нужно реализовать метод __mul__, наш класс это первый операнд, то есть слева.

myType * 3

2. Если множитель слева, то вам нужно реализовать метод __rmul__, наш класс это второй операнд, справа.

3 * myType

Всё тоже самое можно делать и для других математических операторов. И если в этом примере действие и результат будут фактически одинаковыми, то бывают ситуации, когда это не так.
Например, при умножении матриц имеет значение порядок операндов.
Для других операторов, таких как деление или сдвиг, очень важно кто с какой стороны находится.

>>> 2/4, 4/2
(0.5, 2.0)

>>> 2<<3, 3<<2
(16, 12)

>>> 100%15, 15%100
(10, 15)

#tricks #basic
💬 Теперь у канала есть группа для обсуждений!

Создание такой группы не было в приоритете для канала. Но это обязательное условие для возможности комментировать посты. Ведь комментарий это просто ответ на пост в группе.
Добавляю группу в качестве эксперимента и именно для комментариев. Если вдруг всё это превратится в неконтролируемый спам, то лучше удалить такое безобразие.
Отсюда правила, которые я прошу соблюдать:

🔸 Пишите только по делу. Флуд наказуем!
🔸 В чат писать только общие вопросы, не относящиеся к какому-либо посту. Остальное в комментарии.
🔸 Соблюдайте все стандартные правила общения и хорошего тона.

Давайте сделаем общение приятным 😉

➡️ Войти в группу

PS. Если внизу новых постов нет кнопки комментирования, то вам следует обновить Telegram.
Сегодня состоялся релиз Python 3.9! 🎉🥳🚀

Для тех кто пишет много кода очень важна краткость. Именно этот релиз добавляет нам удобную запись объединения словарей.
Вместо такой записи:

d3 = {**d1, **d2}

теперь можно писать так:

d3 = d1 | d2

Конечно это не единственная новая фича.
Остальные подробности на странице релиза:
https://www.python.org/downloads/release/python-390/
А вы ждёте Qt6 как жду его я? )))
Наверняка, те кто ждёт, уже в курсе, но я уточню даты релизов.

- Qt 6.0 Feature freeze - 31.8.2020
- Qt 6.0 Alpha - 2.10.2020
- Qt 6.0 Beta 1 - 15.10.2020
- Qt 6.0.0 RC - 17.11.2020
- Qt 6.0.0 Final - 1.12.2020

Полный список https://wiki.qt.io/Qt_6.0_Release

Между тем, библиотеки PySide3 ждать не стоит. Дело в том, что разработчики решили синхронизировать версии библиотек C++ и Qt for Python.
Так что ждём сразу PySide6!

#qt
🖨 Зачем подменять стандартную функцию print()?

В Python3 директива print() стала функцией, то есть объектом, с которым мы можем делать что угодно. Во 2-м Python мы такого лишены.

Как можно изменить стандартное поведение этой функции?
Обычно подменяют объект sys.stdout, что работает и во 2-м. Но будем действовать через builtins. При этом изменим только функцию print(), а stdout останется прежним.

Что нам потребуется сделать?

1. Создать функцию, которая заменит print и не сломает стандартное поведение
2. Подменить объект builtins.print

import builtins
_print = builtins.print
builtins.print = lambda *a, **kw: _print(*a, **kw)

В примере выше я заменил print но не добавил никакого функционала. Сейчас print() работает как и прежде, только через посредника.

Давайте думать варианты.

🔸 Изменить аргументы по умолчанию.
Порой во время разработки приложения требуется именно распечатывать информацию в консоль через print(). Если это сервер, то всякий раз приходится дописывать flush=True чтобы очищался буфер вывода и мы видели в консоли текст сразу же. Давайте сделаем так чтобы этот аргумент по умолчанию был True. Тогда лямбда будет выглядеть так:

lambda *a, **kw: _print(*a, **{**kw, 'flush': True})

🔸 Поиск принтов в коде.
Приходилось пару раз рефакторить код сервера с очень запутанной структурой. На этапе избавления от принтов я не мог найти где распечатывается пустой список?! Ни одного принта по проекту нет а он распечатывается😢 Давайте заставим функцию print() сообщать нам где она находится.

import builtins, sys
_print = builtins.print

def _located_print(*args, **kwargs):
_print(*args, **kwargs)
f = sys._getframe().f_back
_print('=> File:', f.f_code.co_filename,
'| line', f.f_lineno)

builtins.print = _located_print

Выйдет примерно так:

>>> print('Hello')
Hello
=> File: /path/to/script.py | line: 1

🔸 Как-либо модифицировать все принты.
Например добавлять что-то в начале, изменять цвет, заменять символьные смайлы на юникод и тд.
Вот пример от меня. В Python 3.8 добавили возможность через f-string распечатывать имя переменной вместе с её значением:

>>> value = 123
>>> print(f"{value=}")
value=123

Аналогичный функционал я повторил через подмену функции print().
🌎 Полный код здесь

🔸 Отключить принт повсеместно!
Да, бывает и такое нужно). Кроме простого отключения (код сами догадайтесь какой) можно заменять всё на одинаковое сообщение, сделав принт бесполезным. Чтобы не использовали принт в проектах!

import builtins
_print = builtins.print
builtins.print = lambda *a, **kw: _print("Don't use prints!")

Получится что-то такое

>>> print('Debug message')
Don't use prints!

Но это больше похоже на шутку, реализовать которую помогут стартап скрипты, о которых юзер не догадывается 😝

🔸 Заменить все принты на нормальное логирование.
Тоже вариант, но не особо полезный. Лучше писать нормальный логгинг чем так "костылять". Ну а для тренировки можно попробовать реализовать и этот вариант. Думаю, сами справитесь)

_________________________
Стоить ещё учесть пару моментов:

- эту подмену следует делать в самом начале работы приложения, в скрипте с которого начинается запуск. Удобно делать через стартап скрипт. Исходники при этом менять не требуется.

- функция изменится повсеместно во всех модулях на время сессии

- stdout не изменён, то есть обычный логгер будет писать в консоль нормально.

- Мы подменяем функцию на другую, а значит help(print) не покажет нам документацию, __name__ будет неверный и тд. Так что не забывайте использовать functools.wraps

- в моих примерах кое-где не обрабатывается kwargs. Не копируйте вслепую, всегда понимайте что делаете или не делайте вовсе.


Присылайте в коменты свои варианты! 😎

#tricks
В Python можно делать рекурсивные связи.
Например, список вложенный сам в себя:

>>> x = []
>>> x.append(x)
>>> print(x)
[[...]]

Словари вложенные друг в друга:

>>> a = {}
>>> b = {}
>>> a['b'] = b
>>> b['a'] = a
>>> print(a)
{'b': {'a': {...}}}

Зачем это нужно? Лично я пока не сталкивался с подобными задачами, но, скорее всего, они есть)


#tricks
🙄 Разминка для ума!

Треугольник Серпинского, интересная фигура которую построить достаточно просто.

Алгоритм такой:

1. создаём любые 3 точки на плоскости
2. из этих точек случайно выбираем любую, как начальную
3. случайно выбираем любую точку из этих же трёх точек как цель
4. перемещаемся в сторону цели на половину расстояния
5. повторяем бесконечно с пункта 3

Если сделать достаточно много итераций то вырисовывается интересная фигура. Треугольник, в который вписаны более мелкие треугольники. Это самый настоящий фрактал!

Я собрал пример построения такой фигуры на базе Qt.
🌎 Код можно посмотреть здесь.

С помощью paintEvent я рисую точки по озвученному алгоритму. Каждые 10 секунд либо по клику на виджете строится следующий треугольник.

Особенности примера:

🔸 Атрибут Qt.WA_OpaquePaintEvent позволяет сохранить то, что было нарисовано в прошлой итерации. Таким образом мы видим постепенное наполнение точек а не мелькающую одну точку.
🔸 QTimer позволяет создавать отложенные вызовы один раз или с повторением через интервал.
🔸 QColor.fromHsv() позволяет создать рандомный но предсказуемый цвет с помощью HSV схемы. Не слишком светлый и не слишком тёмный но всегда с разный. Рандомизации подвергается только смещение по цветовому кругу (Hue), яркость (Value) и насыщенность (Saturation) можно контролировать отдельно в своих пределах или оставить статичными. Обычный рандом цвета по RGB не даёт такой предсказуемый результат.
🔸 Каждый новый цикл с новым треугольником предварительно затемняет предыдущие через этот вызов
painter.fillRect(rec, QColor(0, 0, 0, 100))
То есть полупрозрачный цвет. Таким образом, чем старше треугольник, тем он темней.
Если сделать виджет фулскрин, то у нас получится некий ScreenSaver)))
🔸 Да, я знаю, что рисование в Qt не самый лучший способ сделать этот пример) Скорее всего самый НЕподходящий. Попробуйте сделать тоже самое но другими средствами.

#qt #source #tricks
А давайте познакомимся с аудиторией канала поближе!
Для начала определим уровень владения Python и программированием в целом. Это поможет мне лучше выбирать контент!
Напиши, какой твой уровень в Python?
Final Results
45%
🐥 Базовый. Только начал изучать Python.
47%
🤪 Средний. Пишу уверенно, но нужно еще учиться.
6%
😎 Профи. Давно работаю профессионально.
1%
🤓 Ментор. Преподаю Python как эксперт.
Python Заметки pinned «А давайте познакомимся с аудиторией канала поближе!
Для начала определим уровень владения Python и программированием в целом. Это поможет мне лучше выбирать контент!
Напиши, какой твой уровень в Python?
»
Давайте немного прокачаем стандартный словарь. Зачем? А потому что Python это позволяет)))
Добавим возможность работать с ключами словаря как с атрибутами объекта. Для этого нам потребуется определить три метода:

__getattr__ - получение атрибута
__setattr__ - изменение атрибута
__delattr__ - удаление атрибута

Наследоваться будем от стандартного словаря

class _AttribDict(dict):
def __getattr__(self, name):
return self[name]

def __setattr__(self, name, value):
self[name] = value

def __delattr__(self, name):
del self[name]

Подменяем стандартный словарь в одну строку

__import__('builtins').__dict__['dict'] = _AttribDict

Всё, можно тестить 😬

>>> obj = dict(name='Object1', id=14)
>>> print(obj)
{'name': 'Object1', 'id': 14}

Выглядит как обычный словарь

>>> obj['name'] = 'Thing 1'
>>> print(obj['name'])
Thing 1

Изменение ключей работает. То есть стандартное поведение не сломали. А теперь попробуем обратиться к атрибутам

>>> print(obj.name)
Thing 1

Пробуем изменить ключ через атрибут

>>> obj.name = 'Thing 2'
>>> print(obj.name)
Thing 2

И удаляем ключ через атрибут

del obj.name
print(obj)
{'id': 14}

Теперь с ключами словаря можно работать и обычным способом:

dict['key']

и как с атрибутами:

dict.key

Выглядит интересно.
Но помните , если Python позволяет это делать, это не значит что это можно делать 👮‍♂️!
Подобные эксперименты лучше оставить для развлечения и не допускать в прод.
Хорошая разминка, не более того.

#tricks