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

Контакт: @paulwinex

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

Хештеги для поиска:
#tricks
#libs
#pep
#basic
#regex
#qt
#django
#2to3
#source
#offtop
Download Telegram
Python 3.9 готовит нам приятный сюрприз в PEP584.
Ранее мы обсуждали как можно удобно сложить вместе два словаря получив новый словарь. Было много вариатов, но ни одного идеального. Наконец-то в Python добавили оператор для слияния словарей!!! Это оператор "|".
А это значит, что начиная с 3.9 соединить словари можно таким синтаксисом:

dct3 = dct1 | dct2

Чтобы обновить словарь, можно использовать такой синтаксис

dct1 |= dct2

Данный функционал уже можно опробовать, установив первые релизы.

#pep
Есть такое понятие как Switch Statement. Это некоторая конструкция в языке программирования предназначенная для множественного ветвления алгоритма.
вот примеры реализаций в разных языках:

JavaScrpt
C++ (или здесь)
C#
Ruby
PHP
Go
Delphi
и даже Pascal

В целом, шаблон такой:

switch query:
case match1:
...
case match1:
...

А что у нас в Python?

if condition1:
...
elif condition2:
...
elif condition2:
...
else:
...

Вполне рабочий вариант. Но явно отличается от примеров выше.

И тут внезапно!!! 23 июня 2020г выходит в свет PEP622
И что мы видим? Планы на Python 3.10 по добавлению Switch Statement! Называется он Structural Pattern Matching, но по сути мы получаем тот же синтаксис что и в Switch Statement.

match some_expression:
case pattern_1:
...
case pattern_2:
...

В данный момент статус его еще Draft. Интересно как он еще изменится и доживет ли концепция до релиза? Учитывая что один из автором сам Guido van Rossum, можно сказать что внедрят точно!
Пока рано его разбирать, просто подождем...

#pep
Словарь это очень распространённый тип данных в Python.
Он присутствует буквально в каждом скрипте.
Именованные аргументы (kwargs), атрибуты объекта (ˍˍdictˍˍ), любые неймспейсы и тд.

Одна из основных особенностей словаря была в том, что это неупорядоченное множество. То есть порядок добавления ключей не гарантирует что они сохранятся в той же последовательности. Но всё изменилось в Python3.6. Как это произошло?

Словарь, как часто используемый тип данных, стараются максимально оптимизировать. Про одну из таких оптимизация нам рассказывает PEP468 - Preserving the order of **kwargs in a function.

Хм, причем здесь оптимизация?

Всё начинается с отдельной имплементации Python под названием PyPy. В этой версии интерпретатора сделали довольно хорошую оптимизацию словарю.
Показательно разница описана на этой странице

Если вкратце, то дело вот в чём.
Словарь на стороне С это массив. Каждый элемент это тоже массив из 3х элементов (хеш ключа, ключ и значение).
Раньше, чтобы всякий раз при обновлении словаря не изменять размер массива в С (это затратно по времени), изначально он делался с запасом. Как только массив заполняется, его еще увеличивают с запасом, обычно на 1/3. При этом элементы, еще не занятые данными, заполнялись пустышками (полный пример на странице по ссылке выше)

entries = [
['--', '--', '--'],
[-8522787127447073495, 'barry', 'green'],
['--', '--', '--'],
['--', '--', '--'],
['--', '--', '--'],
[-9092791511155847987, 'timmy', 'red'],
['--', '--', '--'],
[-6480567542315338377, 'guido', 'blue']
]

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

indices = [None, 1, None, None, None, 0, None, 2]
entries = [[-9092791511155847987, 'timmy', 'red'],
[-8522787127447073495, 'barry', 'green'],
[-6480567542315338377, 'guido', 'blue']]

Именно этот принцип повторили в Python 3.6. Что мы получаем в итоге?

🔸 Увеличилась скорость поиска и добавления ключей.
🔸 Сократился расход памяти в 3 раза

Python 2.x-3.5

>>> d = {x: x*2 for x in range(100)}
>>> d.ˍˍsizeofˍˍ()
12536

Python 3.6

>>> d = {x: x*2 for x in range(100)}
>>> d.ˍˍsizeofˍˍ()
4680

Ведь теперь вместо элемента ['--', '--', '--'] у нас просто None, который, кстати, является одним и тем же объектом где бы он не использовался.

🔸 Как бонус (или как побочный эффект), мы получаем упорядоченность ключей.

То есть одним выстрелом завалили трёх мамонтов!

#pep
👍1
В PEP509 описано добавление в структуру данных словаря приватного поля с версией. Что это за версия? Она нужна для ускорения проверки изменений в словаре. Разные механизмы должны следить за целостностью данных (например неймспейса, который суть словарь). Чтобы каждый раз не проверять изменился ли словарь, мы просто можем проверить его версию.

На стороне реализации С в структуру данных словаря добавлена приватная переменная ma_version_tag, которая изменяется всякий раз при изменении словаря.

clear()
pop(key)
popitem()
setdefault(key, value)
__delitem__(key)
__setitem__(key, value)
update(...)

Если вызван один из этих методов, то версия изменяется. Версия это не хеш и не ID. Каждый словарь имеет свою уникальную версию, даже два одинаковых или два пустых словаря.

Как посмотреть версию? Из самого словаря не получится. Есть код в тестах для получения свойства ma_version_tag, используется для прогонки тестов.
Чтобы попробовать этот код достаточно повторить то что написано в тестах.

Для Windows следует добавить директорию Lib\test в PYTHONPATH.

>>> import _testcapi
>>> d1 = {}
>>> d2 = {}
>>> _testcapi.dict_get_version(d1)
12083
>>> _testcapi.dict_get_version(d2)
12099

Интересно то, что версия изменится даже если данные будут одинаковыми. Главное сам факт изменения.

>>> d = {1:2}
>>> _testcapi.dict_get_version(d)
12200
>>> d[1] = 2
>>> _testcapi.dict_get_version(d)
12239

Таким образом мы можем узнать а не пытался ли кто-то что-либо сделать с нашим словариком?

Жаль только нет стандартного способа получения версии (или я не нашел?). Я думаю применение нашлось бы)

#pep #tricks
Какие ассоциации у вас вызывает число 404?
Сразу вспоминается ошибка 404 Not Found (не найдено).

Именно такой номер имеет PEP 404 Python 2.8 Un-release Schedule для несуществующего релиза Python 2.8.

В нём нам сообщают что релиз 2.8 никогда не выйдет и даются советы как перейти на ветку 3.х.

#pep #2to3
Почему рекомендуют каждый импорт делать на новой строке?
Просто так написано в PEP8, скажете вы. Да, это действительно так. Но PEP8 это стилистические рекомендации. Какая практическая польза от такой записи?
Несколько раз мои студенты спрашивали зачем писать длинней когда можно короче? Не это ли один из основных принципов в Python?

Как мы хотим писать:

import os, sys, subprocess

Как рекомендуют

import os
import sys
import subprocess

Если "нельзя" писать в одну строку, то зачем добавили такую возможность - перечислять имена импорта?

Объясняю в два этапа:

🔸Перечисление добавлено для возможности импорта через from, когда из модуля требуется импортнуть несколько имён

from os.path import join, expanduser, sep

В PEP328 добавили возможность перечисление вставлять в скобки чтобы переносить на новую строку без экранирования символа новой строки

Было

from os.path import join, expanduser, \
sep, basename, exists

Стало

from os.path import (join, expanduser,
sep, basename, exists)

🔸 Практическая польза от импортов на разных строках заметна когда вы делаете слияние разных веток кода с конфликтами или просто с изменениями.

Допустим, есть два варианта кода

# file1.py
import fnmatch
import time
import json
import uuid

#file2.py
import fnmatch, time, json, uuid

Зачем-то нам потребовалось изменить импорт, вместо uuid импортим sys. Что покажет нам diff?

# file1.py
-import uuid
+import sys

# file2.py
-import fnmatch, time, json, uuid
+import fnmatch, time, json, sys

Заметили разницу? Пусть даже для GIT это совершенно не проблема, кодревью будет происходить удобней с точки зрения человека. Да, это объяснение человека, который сам делает кодревью (и регулярно сам же нарушает эту рекомендацию, о чем потом жалеет 😭)

Есть ли у вас еще доводы в пользу каждого импорта на новой строке?

#tricks #pep
По аналогии с PEP у Django есть DEP.
Самый интересный для меня на данный момент на это DEP 0009: Async-capable Django. Он про то, как будет внедряться поддержка аснихронности.

Начиная с версии 3 в Django начали появляться асинхронные плюшки. Это всё еще мало чтобы делать асинхронное приложение, но долгий путь начинается с одного маленького шага!

Всё должно пройти в несколько этапов и к 4й версии обещают сделать Django асинхронным!

Что это даёт разработчикам в случае если весь фреймворк станет поддерживать async?

- Ускорение работы web-приложения? Если правильно писать асинхронный код, то да.

- Усложнение кода? Возможно, но фреймворк на то и фреймворк, чтобы прятать сложности где-то внутри. Надеюсь код усложнится не сильно, посмотрим...

И когда нам этого ожидать? Судя по этой схемке Django 4 выйдет в Декабре 2021 года. А это значит, что у вас есть примерно год чтобы научиться понимать асинхронный код, если еще не умеете😁

#django #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
Для Python3.8 в PEP0578 добавили функционал аудита Runtime операций. Это позволяет выполнять хуки (функции) при возникновении определённых событий в интерпретаторе.

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

Полный список стандартных аудит-ивентов можно посмотреть здесь. Из названий становится ясно что мы можем перехватить. Например, мы можем перехватить факт открытия файла (open), импорта модуля (import), копирование файла (shutil.copyfile), запуск процесса (subprocess.Popen) и тд. Как минимум мы можем залогировать данное событие, как максимум, вызвать аварийное завершение программы.

Примеры использования:

▫️Представим, что после разработки и долгих тестирований веб сервиса вы могли где-то оставить функцию ручного ввода данных в консоль. На продакшене такое недопустимо. С помощью аудита можно вызвать исключение перехватив ивент builtins.input

▫️С помощью ивента socket.getaddrinfo можно определить на какие сайты юзер заходил с помощью вашего приложения.

▫️Ивент exec позволит проверить загруженный объект кода перед его исполнением. Например выявить потенциально опасный код, или оставленные API ключи в строковых переменных.

Как добавить свой хук?

Для первого теста выполните такой код в самом начале работы приложения

import sys

def hook(event, args):
print(f'EVENT: {event}{args}')

sys.addaudithook(hook)

Каждый хук вызывается для всех событий, поэтому мы можем с помощью одного хука увидеть всё что происходит в интерпретаторе.
Теперь давайте посмотрим какие web-коннекты создаются при работе нашего кода. Для запросов используем requests.

import sys
import requests

def socket_hook(event, args):
if event == 'socket.getaddrinfo':
print(args[0])

sys.addaudithook(socket_hook)
requests.get('https://google.com')

В аутпуте вы увидите домен, на который был сделан запрос.

А так же:
▫️есть платформозависимые хуки (например взаимодействие с winapi)
▫️можно писать хуки на Си
▫️так как это мера для обеспечения безопасности, нет способа удалить хуки после добавления.

Напоминаю, доступно в Python3.8+

#pep #tricks
Debian 12 не позволяет глобально устанавливать Python пакеты через pip. Не поможет даже sudo.
Такое поведение описано в PEP 668.
Это сделано для минимизации конфликтов версий системных пакетов.
Если вам действительно нужно поставить что-то глобально, используйте
apt install python-packagename
В остальных случаях всегда используйте виртуальное окружение.

#pep
👍18
В Python 3.14 появится реализация PEP 750 и новый способ форматирования: t-strings. Это так называемые Template Strings.

Синтаксис такой же как с f-strings, но форматирование происходит не сразу.
Вместо строки создаётся объект Template, который внутри себя содержит исходную информацию, сырую строку (template.strings) и переменные (template.values).
Это позволяет произвести дополнительную обработку данных перед форматированием, например для усиления безопасности.
В примерах можно увидеть как строка с HTML кодом дополнтиельно обрабатывается чтобы избежать инъекции JS кода за счет экранирования служебных символов.

Конечно, этим примером возможности не ограничивюатся. Более подробно про функционал будет понятно ближе к релизу в конце года. Сейчас доступно в сборках 3.14.0a7+ из этой ветки.

Простой пример создания шаблона

name = "World"
template = t"Hello {name}!"


Что является шорткатом для
from string.templatelib import Template, Interpolation

template = Template(
"Hello ",
Interpolation(value="World", expression="name"),
"!"
)


В обоих случаях объект получим идентичный
print(isinstance(template, Template))
# True
print(template.strings)
# ("Hello ", "!")
print(template.values)
#(name,)


Больше примеров ➡️ здесь

#pep
👍8🤔4👎2🔥1😢1