PYTHON IN DEPTH🐍
448 subscribers
3 photos
10 links
ПАЙ8
Download Telegram
Введение в множества

Set' ы в Python реализованы так, что максимально напоминают математические множества. Давайте пройдемся по основным свойствам и возможностям множеств в Python, и разберемся, как их использовать.

В математике множество -- это набор объектов произвольной природы. В Python множество тоже может содержать переменные разных типов, например:

>>> A = {"My hovercraft is full of eels", 42, (3.14, 2.72)}

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

>>> A.add([-1, 0])
TypeError: unhashable type: 'list'

При этом поскольку сами множества мутабельны, то множество множеств, как в известном парадоксе про брадобрея, сделать не получится. Но если очень хочется, можно использовать frozenset’ы: эти объекты в остальном ведут себя почти так же, но они иммутабельны и их можно добавить в множество.

Инициализировать множество можно используя фигурные скобки или через конструктор класса set(). Эти инициализации эквивалентны:

>>> B = {1, 2, 3}
>>> B = set((1, 2, 3))

Только не запутайтесь: такая инструкция

C = {}

создаст не множество, а словарь.

В версиях интерпретатора 2.7 и выше работают set comprehensions:

>>> C = {x for x in range(1, 5)}
>>> C
{1, 2, 3, 4}

Элементы множества, как и в математике, должны быть уникальными. На практике этим пользуются для того, чтобы исключить повторяющиеся элементы:

>>> D = [1, 2, 3, 3]
>>> D = list(set(D))
>>> D
[1, 2, 3]

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

>>> for elem in C:
... print(elem)

Над множествами в Python можно делать те же операции, что и в математике: находить объединение, пересечение, проверять принадлежность к множеству и так далее. Для этого можно пользоваться операторами, а можно методами множеств:

A | B A.union(B)
A & B A.intersection(B)
A - B A.difference(B)
A <= B A.issubset(B)
A => B A.issuperset(B)


Обратите внимание, что операторы принимают только set’ы, а методы -- любые iterable контейнеры. Есть мнение, что операторы менее читаемые, но оба подхода в целом равноправны.

И еще у класса set есть методы, которые удобны для работы со множествами как с коллекциями:

add() — добавить элемент,
remove() — удалить элемент,
pop() — извлечь с удалением,
update() — объединить с другим множеством,
clear() — очистить множество.

И напоследок: один раз я больно отстрелила себе ногу, когда хотела добавить составной элемент в множество с помощью неправильного инструмента. Например, если у нас есть множество строк и мы пытаемся добавить в него еще один элемент вот так:

>>> F = {"Seregia", "Vasia"}
>>> F.update("Alisa")

то получаем ожидаемый результат:

>>> F
{'l', 'a', 'Seregia', 'Vasia', 'A', 'i', 's'}

Это абсолютно валидный код и он отработает, поэтому такую ошибку по невнимательности можно искать довольно долго. Так что не попадайте в ловушку методов add() и update().

В общем, Python set'ы сильно напоминают математические множества: могут содержать объекты разных типов, требуют уникальности элементов, не сохраняют порядок и имеют методы, позволяющие их объединять, пересекать, etc... Кроме того, множества имеют интерфейс для работы с ними как с коллекциями.

Как определиться в выборе коллекции?
🐙 Если важен порядок — используйте списки.
🐙 Если нужно отображение ключ-значение — используйте словари.
🐙 Если нужен набор уникальных элементов — используйте множества.
👍5🔥2🙏1🕊1
Умножение контейнеров

Один интересный факт из Python. Вы, наверное, знали, что для строк определен оператор умножения и что можно делать так:

>>> 'привет ' * 4
'привет привет привет привет '

То же, вообще говоря, работает с другими контейнерами, например, с кортежами:

>>> ('привет',) * 3
('привет', 'привет', 'привет')

И с этим все хорошо, пока внутри контейнера не появляется ссылка на изменяемый объект. Например, на словарь:

>>> d = [{'key': 'val'}] * 2
>>> d
[{'key': 'val'}, {'key': 'val'}]

Потому что теперь каждый из двух словарей доступен по одной и той же ссылке:

>>> d[0]['key'] = ''
>>> d
[{'key': ''}, {'key': ''}]

Упс, отстрелили себе ногу. Умножение -- это валидный способ создавать составные объекты только если вложенные неизменяемы. 🐠
👍4🔥1
Гид по множественному присваиванию

Уверена, что множественное присваивание в Python видели все. Выглядит оно вот так:

>>> x, y, z = 1, 2, 3
>>> x
1
>>> z
3

На самом деле на низком уровне создается tuple и в цикле инициализируется значениями 1, 2, 3. Поэтому с тем же успехом можно не опускать скобки и написать вот так:

>>> (x, y, z) = (1, 2, 3)
>>> x
1
>>> z
3

И это тоже сработает. Поэтому множественное присваивание еще называют tuple unpacking. Да и чаще всего его, кажется, используют именно с кортежами. Но вообще так можно писать с любыми iterable типами:

>>> x, y = [1, 2]
>>> y
2

И со строками тоже:

>>> x, y = 'hi'
>>> x
'h'
>>> y
'i'

Множественное присваивание, если применять его с оператором *, позволяет делать еще вот такие крутые штуки:

>>> numbers = [1, 2, 3, 4, 5, 6]
>>> first, *rest = numbers
>>> first
1
>>> rest
[2, 3, 4, 5, 6]

По сути * здесь заменяет слайсы. С тем же успехом можно было написать:

>>> first, last = numbers[0], numbers[1:]
>>> first
1
>>> last
[2, 3, 4, 5, 6]

Но через распаковку получается читаемее. Можно даже <<вырезать>> середину:

>>> first, *middle, last = numbers

И если вам не нужны все промежуточные индексы, то хороший тон это вообще использовать нижнее подчеркивание:

>>> first, *_, last = [1, 2, 3, 4, 5, 6]

Кстати, если речь о вложенных объектах, то обратите внимание, что копия будет глубокой:

>>> color, point = ('blue', (0, 0, 255))
>>> color
'blue'
>>> point
(0, 0, 255)

В некоторых случаях это очень удобно.

В общем, множественное присваивание можно использовать не только с кортежами, но и списками, строками и любыми iterable типами. Его плюс в том, что оно позволяет не использовать индексы, а значит, уменьшает склонность к ошибкам и делает код читаемее. А еще оно используется в <<args, kwargs>> синтаксисе, который часто смущает начинающих, но об этом в следующий раз. 🐠
👍7🤔1
Введение в декораторы

В нескольких следующих постах я хочу поговорить о декораторах. Будет базовое определение, мотивация их использовать, всякие хитрости, а еще куча примеров.

Прежде, чем говорить о декораторах, нужно кое- что узнать о функциях в Python. Допустим, у нас есть функция, которая здоровается с Юпи:

def hey_Jupi():
print("Привет, Юпи!")

Функции в Python -- это объекты первого класса, ничем не хуже, чем int'ы или словари. Это значит, что:

🐙 Функцию можно присвоить переменной:

say_hi = hey_Jupi
say_hi()
# Привет, Юпи!

🐙 Функцию можно вернуть из функции:

def wrapper(func):
print("Юпи пришла.")
return func

hello_Jupi = wrapper(hey_Jupi)
# Юпи пришла.
hello_Jupi()
# Привет, Юпи!

🐙 Функцию можно определить внутри другой функции:

def deco(func):
def wrapper():
print("Юпи пришла.")
func()
return wrapper

hey_Jupi = deco(hey_Jupi)
hey_Jupi()
# Юпи пришла.
# Привет, Юпи!

Смотрите, что получилось на последнем шаге. На этапе создания deco никакой код не выполняется -- мы заходим в deco, видим, что здесь определена функция wrapper и возвращаем ее. Таким образом мы подменяем исходную hey_Jupi на wrapper и получаем новое поведение hey_Jupi, не изменяя ее код!

Это и называется декоратор. Это настолько удобный и мощный инструмент, что в Python для него придумали специальный синтаксический сахар. При условии, что функция deco у нас уже определена так же, как выше, можно добавить название декоратора с символом @ перед определением функции и получить эквивалентное поведение:

@deco
def hey_Jupi():
print("Привет, Юпи!")

hey_Jupi()
# Юпи пришла.
# Привет, Юпи!

Кстати, этот же декоратор можно применить и к любой другой функции:

@deco
def take_five():
print("Юпи, дай пять!")

take_five()
# Юпи пришла.
# Юпи, дай пять!

Декораторы круты тем, что позволяют гибко модифицировать поведение функции, применять одну и ту же модификацию к нескольким функциям сразу и даже менять поведение функций, доступа к коду которых у нас нет! Зачем нам декораторы на реальных проектах?

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

В следующих постах разберемся, как комбинировать декораторы и передавать в декоратор параметры. Всем пять!🐠
👍6🔥1
Комбинируем декораторы

Допустим, у нас есть функции, которые работают со строками. Возьмем, например, функцию, которая возвращает строчку из Zen of Python:

def readability():
return 'Readability counts'

print(readability())
Readability counts

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

def bold(func):
def wrapper():
return '<b>' + func() + '</b>'
return wrapper

def italic(func):
def wrapper():
return '<i>' + func() + '</i>'
return wrapper

Как уже договаривались в прошлом посте запись:

@bold
def readable():
return 'Readability counts'

Эквивалентна записи

readable = bold(readable)

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

print(readable())
<b>Readability counts</b>

А что если мы хотим получить текст одновременно и жирный и с курсивом? Можно сделать композицию декораторов:

@italic
@bold
def readable():
return 'Readability counts'

print(readable())
<i><b>Readability counts</b></i>

Обратите внимание, что результат в зависимости от порядка применения декораторов будет разным:

@bold
@italic
def readable():
return 'Readability counts'

print(readable())
<b><i>Readability counts</i></b>

Так получается из-за разного порядка выполнения функций: в первом случае мы сначала добавляем тэг <<жирный>>, а сверху оборачиваем в <<курсив>>:

readable = italic(bold(readable))

А во втором -- наоборот:

readable = bold(italic(readable))
👍3🔥3
Исключения
Представьте, вы приступаете к работе над новым проектом и обнаруживаете в логах следующие сообщения, которые смешаны с успешными запросами к серверу:

Unexpected exception:

Что-то постоянно ломается, но без каких-либо сообщений.

Если сократить проблему до минимального примера из кода приложения, с которым я недавно столкнулся, получится следующая конструкция, которая на первый взгляд не вызывает подозрений:

try:
raise KeyError
except Exception as e:
print(f"Unexpected exception: {e}")

При выполнении этого примера получим следующий результат:

Unexpected exception:

Проблема заключается в том, что есть четыре способа вывести сообщение об исключении:

🐟 print(e)
🐟 print(str(e))
🐟 print(e.message)
🐟 print(repr(e))

Первые два варианта не особенно информативны. Например, если мы попытаемся обратиться к несуществующему ключу в словаре (самая распространенная ошибка в Python), то эти варианты выведут только название ключа.

my_dict = {}
try:
b = my_dict["bad"]
except Exception as e:
print(f"Unexpected exception: {e}")

Unexpected exception: 'bad'

Это происходит потому, что str(e) и e выводят только сообщение об ошибке, но не ее тип. Чаще всего этого достаточно, но исключения существуют для того, чтобы информировать о ситуациях, которые не были ожидаемы.

Иногда люди используют print(e.message). Однако здесь возникают две проблемы: во-первых, мы все равно получаем только сообщение об ошибке, а не ее тип. Во-вторых, атрибут message не определен для всех исключений. Если мы не проверим наличие этого атрибута перед использованием, мы получим новое исключение:

AttributeError: 'KeyError' object has no attribute 'message'

Однако магический метод repr, который используется для предоставления наиболее точного описания, справляется с задачей. Рассмотрим сравнение:

try:
raise KeyError
except Exception as e:
print(f"Unexpected exception: {repr(e)}")

Unexpected exception: KeyError()

В случае с использованием словаря получим:

Unexpected exception: KeyError('bad')

что является более явным, чем все три предыдущих варианта
2🔥2
Еще про исключения:

repr(e) -- это, конечно хорошо, но ведь есть ещё лучше, а именно:

from traceback import print_exc
...
my_dict = {}
try:
b = my_dict["bad"]
except Exception as e:
print_exc()

что выведет:

Traceback (most recent call last):
File "<pyshell#1>", line 2, in <module>
KeyError: 'bad'

и при этом программа продолжит свою работу. А если ошибку надо выводить не в stdout, то можно сделать так:

from traceback import print_exc
from io import StringIO
...
try:
b = my_dict["bad"]
except Exception:
buffer = StringIO()
print_exc(file=buffer)
out_var = buffer.getvalue()
🔥3👍2
Дублирование объектов в множестве

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

Давайте создадим класс Client и добавим его в коллекцию (я также добавил метод repr для красивого вывода):

class Client:
def init(self, user_name):
self.user_name = user_name

def repr(self):
return self.user_name

fish1 = Client(user_name="catfish")

clients = set()
clients.add(fish1)

print(clients) # {catfish}

Замечательно, всё работает как задумано! Теперь попробуем добавить второго клиента и убедимся, что дублирования не происходит:

fish2 = Client(user_name="catfish")
clients.add(fish2)

print(clients) # {catfish}

Как это возможно? Мы добавили два абсолютно идентичных экземпляра в коллекцию, ожидая, что останется только один, но оба остались.

При добавлении объекта в коллекцию интерпретатор следует следующему правилу:

если a == b, то hash(a) == hash(b) должно быть обязательно выполнено.

То есть, интерпретатор сравнивает объекты не только напрямую, но также сравнивает их хеши.

Для сравнения объектов, коллекции и словари используют магический метод eq. Для пользовательских объектов этот метод определен по умолчанию. По умолчанию, два разных объекта не считаются равными, даже если они абсолютно идентичны:

print(fish1 == fish2) # False

По умолчанию все объекты в Python также имеют хеш-значение (хеш), которое рассчитывается из их идентификатора (id). Когда мы пытаемся добавить объект в коллекцию, мы используем магический метод hash этого объекта, который также определен по умолчанию. Часто люди думают, что хеш объекта совпадает с его адресом в памяти, но это не всегда так:

🦑 В Python 2.6 и более ранних версиях hash(x) = id()
🦑 В Python 2.6 и более поздних версиях: (https://bugs.python.org/issue5169) hash(x) == id(x)/16

То есть, нельзя полагаться на то, что hash(x) = id().

Однако можно полагаться на то, что в Python у объектов по умолчанию есть хеш-значение, которое зависит от их идентификатора (id), и что хеш объекта не меняется в течение его жизни.

Так как в нашем примере объекты разные и занимают разные ячейки памяти, мы получаем:

print(hash(fish1), hash(fish2)) # 8786876890805 8786876904409

Так что можно сделать, чтобы объекты, которые мы считаем одинаковыми, объединялись в коллекции? Нужно переопределить методы hash и eq, чтобы явно указать интерпретатору, как сравнивать объекты и как рассчитывать хеш.

Для этого внесем изменения в определение класса:

class Client:
def init(self, user_name):
self.user_name = user_name

def repr(self):
return self.user_name

def hash(self):
return hash(self.user_name)

def eq(self, other):
if self.user_name == other.user_name:
return True
else:
return False

Теперь, при добавлении объектов в коллекцию, всё работает так, как ожидалось:

clients = set()

fish1 = Client(user_name="catfish")
clients.add(fish1)
fish2 = Client(user_name="catfish")
clients.add(fish2)

print(clients) # {catfish}

Помните, чтобы добавить объект вашего собственного класса в коллекцию или словарь в Python, вам необходимо:

🦑 Переопределить и метод hash, и метод eq, иначе это не будет работать (https://docs.python.org/3/reference/datamodel.html#object.__hash__)
🦑 Если объекты идентичны, их хеши должны быть равными.
🦑 Хеш-функция должна содержать информацию, которая однозначно идентифицирует объект.
🦑 Хеш объекта не должен изменяться в течение его жизни (иначе могут возникнуть проблемы во время выполнения программы).
👍51
🖥Microsoft интегрировала Python в Excel

Все популярные библиотеки Python (pandas, statsmodels и matplotlib) стали доступны для создания графиков и диаграмм. Для реализации всего этого появилась новая функция "PY"
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥3
Топ популярных горячих клавиш, для PyCharm:

Ctrl + D
— Дублировать строку, когда пишешь схожие строки, теперь нет надобности набирать их сначала или выделять и копировать.
Ctrl + R — Решил переименовать класс? Изменит имя во всем проекте.
Ctrl + Shift + N — Поиск класса или метода по всему проекту.
Ctrl + Alt + M — Написали код, теперь захотели его обернуть в функцию, вот сочетание.
Ctrl + Alt + S — Перейти в настройки.
Ctrl + Y — Удалить строку.
Ctrl + B — Переместиться к данному классу.
Ctrl + F12 — Показывает структуру данных файла.
Alt + F7 — Посмотреть где используется данный класс, метод или функция.
Ctrl + Shift + U — Быстро изменить регистр слов.
Ctrl + Alt + L — Быстрое форматирование кода по стандарту PEP 8.
Ctrl + Shift + ↑ ↓ — Для быстрого перемещения строк или блоков.
Ctrl + W — Выделить текущий блок.
9👍7🔥2
Forwarded from Хитрый Питон
Вышла первая бета Django 5.0, а значит значимого изменения состава релиза уже не будет и можно смотреть, что завезли:

1. Много поддержки асинхронности - в contrib.auth, возможность получить и обработать asyncio.CancelledError внутри вьюхи, если клиент разорвал соединение до того, как мы закончили обрабатывать запрос, поддержка асинхронки в куче декораторов, асинхронная отправка сигналов, новые асинхронные методы у моделек
2. На первом месте довольно спорная фича - возможность показывать количество фильтруемых объектов в боковом фильтре в админке. Там под капотом COUNT и естественно на более-менее приличных объемах данных тормозить будет нечеловечески. Благо можно глобально отключить
3. Упрощенная шаблонизация для форм из коробки, для тех кто работает с html-формами код станет читабельнее (хотя думается мне, что те кто работает с большими формами уже давно что-то подобное у себя реализовали)
4. Возможность задавать дефолты на уровне базы данных
5. `GENERATED`-поля в моделях, значение которых рассчитывается на уровне БД
6. В Choice-полях теперь можно использовать словарь, вместо кортежей

В общим никаких революций https://docs.djangoproject.com/en/5.0/releases/5.0/
🔥31👍1🫡1
Forwarded from Хитрый Питон
Новости nogil. Steering Council принял PEP 703 - опциональный gil из коробки. На discuss.python.org большой пост про это, я приведу только заинтересовавшие меня моменты:

Пока нет уверенности, что выпиливание gil не поломает совместимость со сторонними либами, не сделает интерпретатор медленнее на 10–15% и не усложнит его поддержку. Выбрали вполне ожидаемый подход - реализуем и посмотрим, как пойдет, потому что на прототипах и теоретических выкладках далеко не уедешь.

Как и предполагали раньше, все будет происходить в несколько этапов:

1. Возможность отключить gil на этапе сборки — это точно не продакшен решение, а инструмент для авторов библиотек и экспериментаторов
2. Финализация изменений API и ABI, после чего nogil-сборка будет уже считаться не экспериментальной но все еще не включаться по дефолту
3. Отключенный gil по умолчанию с возможностью включить при сборке

В общем планируют переходить очень постепенно и не загонять людей в светлое будущее палкой вплоть до того, что готовы отозвать PEP 703 если переход окажется слишком калечащим.

https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-in-cpython-acceptance/37075
👍32🔥1
Forwarded from Витя пишет код (Victor Luferov)
Еееееее, а че мы над js смеемся
👍2😱2