Библиотека Python разработчика | Книги по питону
19.1K subscribers
1.06K photos
403 videos
82 files
1.04K links
Полезные материалы для питониста по Фреймворкам Django, Flask, FastAPI, Pyramid, Tornado и др.

По всем вопросам @evgenycarter

РКН clck.ru/3Ko7Hq
Download Telegram
Если вам нужно выполнить поиск в отсортированной коллекции, то бинарный поиск — это именно то, что вам нужно. Этот простой алгоритм сравнивает искомое значение с элементом в середине массива; результат определяет, какую половину нужно искать дальше.

Стандартная библиотека Python предоставляет возможность использовать бинарный поиск без его непосредственной реализации. Функция bisect_left возвращает самую левую позицию элемента в отсортированном списке, а bisect_right — самую правую.


from random import randrange
from bisect import bisect_left

n = 1000000
look_for = 555555
lst = sorted(randrange(0, n) for _ in range(n))

%timeit look_for in lst
# 69.7 ms ± 449 µs на цикл

%timeit look_for == lst[bisect_left(lst, look_for)]
# 927 ns ± 2.28 ns на цикл


Результаты демонстрируют, что использование бинарного поиска через bisect_left быстрее, чем стандартный поиск в списке с помощью оператора in.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
📌 О представлении данных в байтах в Python

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

Чтобы записать символы в файл, их нужно преобразовать в байты — это называется кодированием (encoding). Когда вы читаете байты из файла и хотите преобразовать их в понятные символы, этот процесс называется декодированием (decoding).

🔤 Кодировки и их применение

Существует множество методов кодирования. Один из самых популярных — Unicode, но сам по себе Unicode не является кодировкой в традиционном смысле. Unicode определяет соответствие между символами и их числовыми кодами. Например, 🐍 имеет код 128 013.

Однако, чтобы записать числа в файл, нужна настоящая кодировка. Unicode обычно используется с utf-8, которая (в большинстве случаев) является кодировкой по умолчанию в Python. При чтении из файла Python автоматически декодирует данные, используя utf-8.

Если вы хотите использовать другую кодировку, просто укажите её с помощью параметра encoding= в функции open. А чтобы работать с "чистыми" байтами, добавьте символ b к режиму открытия файла.

Пример:

# Кодирование строки в файл
with open('example.txt', 'w', encoding='utf-8') as f:
f.write('Привет, мир!')

# Чтение в байтовом режиме
with open('example.txt', 'rb') as f:
data = f.read()
print(data) # Вывод: b'\xd0\x9f\xd1\x80\xd0\xb8...'


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
🔑 Использование объектов в качестве ключей словаря в Python

В Python вы можете использовать любой объект в качестве ключа словаря, если он реализует метод __hash__. Этот метод возвращает целое число, но при этом важно соблюдать одно ключевое требование: равные объекты должны иметь одинаковый хэш (обратное утверждение необязательно).

👉 Не используйте изменяемые объекты в качестве ключей! Если объект изменяется после добавления в словарь, он становится "невидимым" для поиска, так как его хэш может измениться.

🌀 Странность с отрицательными хэшами

Есть интересная особенность, которая может вас удивить при отладке или написании юнит-тестов. Рассмотрим следующий пример:


class A:
def __init__(self, x):
self.x = x

def __hash__(self):
return self.x


Результаты хэширования экземпляров класса:


>>> hash(A(2))
2
>>> hash(A(1))
1
>>> hash(A(0))
0
>>> hash(A(-1)) # внимание!
-2
>>> hash(A(-2))
-2


💡 В CPython значение -1 зарезервировано для внутренних ошибок. Если хэш-значение равно -1, интерпретатор автоматически преобразует его в -2. Это может вызывать неожиданные проблемы при сравнении или использовании объектов в качестве ключей.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥1
Иногда нужно создать функцию на основе более универсальной. Например, у функции int() есть параметр base, который можно зафиксировать, чтобы получить новую функцию base2:


>>> int("10")
10
>>> int("10", 2)
2
>>> def base2(x):
... return int(x, 2)
...
>>> base2("10")
2


Для более точной и семантически понятной реализации можно использовать functools.partial:


from functools import partial

base2 = partial(int, base=2)


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


>>> list(map(partial(int, base=2), ["1", "10", "100"]))
[1, 2, 4]


Без использования partial пришлось бы писать код так:


>>> list(map(lambda x: int(x, base=2), ["1", "10", "100"]))
[1, 2, 4]


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Метод format в Python для строк — мощный инструмент, поддерживающий множество возможностей, о которых вы, возможно, даже не знали. Каждый заменяемый плейсхолдер ({...}) может содержать три части: имя поля, преобразование и спецификацию формата.

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


>>> '{}'.format(42)
'42'
>>> '{1}'.format(1, 2)
'2'
>>> '{y}'.format(x=1, y=2)
'2'


Преобразование позволяет указать, что вместо str() следует использовать repr() (или ascii()) при преобразовании объектов в строки:


>>> '{!r}'.format(datetime.now())
'datetime.datetime(2018, 5, 3, 23, 48, 49, 157037)'
>>> '{}'.format(datetime.now())
'2018-05-03 23:49:01.060852'


Спецификация формата задаёт, как значения будут представлены:


>>> '{:+,}'.format(1234567)
'+1,234,567'
>>> '{:>19}'.format(1234567)
' 1234567'


Эта спецификация может быть применена и к отдельному объекту с помощью функции format (не метода str):


>>> format(5000000, '+,')
'+5,000,000'


Функция format вызывает метод __format__ объекта, поэтому вы можете изменить его поведение для своих типов.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1💩1
Функция itertools.chain позволяет объединить несколько итерируемых объектов, чтобы работать с ними, как с единым целым:


from itertools import chain

print(list(chain(['a', 'b'], range(3), set('xyz'))))
# Вывод: ['a', 'b', 0, 1, 2, 'x', 'z', 'y']


Иногда нужно проверить, пуст ли генератор (точнее, исчерпан ли он). Для этого можно попытаться получить следующий элемент с помощью next(). Если элемент есть, его нужно вернуть обратно в генератор, но сделать это напрямую невозможно. Однако можно «приклеить» его обратно с помощью chain:


from itertools import chain

def sum_of_odd(gen):
try:
first = next(gen) # Пытаемся получить первый элемент
except StopIteration:
raise ValueError('Empty generator') # Если генератор пуст, выбрасываем исключение

# Используем chain для возврата первого элемента и объединения с остальными
return sum(
x for x in chain([first], gen)
if x % 2 == 1 # Суммируем только нечетные числа
)


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


print(sum_of_odd(x for x in range(1, 6))) # Вывод: 9 (1 + 3 + 5)
print(sum_of_odd(x for x in range(2, 3))) # Вывод: 0 (нет нечетных чисел)
print(sum_of_odd(x for x in range(2, 2))) # ValueError: Empty generator


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
В Python блок else можно использовать не только после if, но и после циклов for и while. Код внутри else выполняется только в том случае, если цикл завершился естественным образом, то есть не был прерван с помощью break.

Наиболее распространённый случай использования этого — поиск элемента в цикле с прерыванием через break, если элемент найден:


# Пример 1: Список содержит нечётное число
first_odd = None
for x in [2, 3, 4, 5]:
if x % 2 == 1: # Проверяем, является ли число нечётным
first_odd = x
break # Прерываем цикл, так как элемент найден
else:
raise ValueError('No odd elements in list') # Выполнится, если цикл завершился без break

print(first_odd) # Результат: 3


Если в списке нет подходящего элемента, цикл завершается естественным образом, и выполняется блок else:


# Пример 2: Список не содержит нечётных чисел
for x in [2, 4, 6]:
if x % 2 == 1:
first_odd = x
break
else:
raise ValueError('No odd elements in list') # Исключение будет поднято

# ValueError: No odd elements in list


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
27 ноября собираемся на Pytup: митап Яндекса для Python-разработчиков и ML-инженеров 🚀

Присоединяйтесь в Екатеринбурге или онлайн, чтобы в неформальной обстановке поговорить о Python, машинном обучении и технологиях, которые двигают индустрию вперед.

В программе выступлений:

> Арсений Саблин, разработчик системы контроля качества умных устройств на производстве (Яндекс Алиса), поделится, как используется Python при тестировании станции с Алисой;

> Никита Улько, техлид VK Tech, разберет чистую архитектуру с практической точки зрения: за что ее ценят и как гибко применять ее принципы, фокусируясь на решении конкретных проблем;

> Егор Гордовский, технический менеджер проектов Yandex Cloud, расскажет о сложном техническом организме, помогающем превратить код в работающий сервис — дата-центре.

Помимо докладов участников в Екатеринбурге ждет дискуссия Snake Pit, а также соревнования по классической «Змейке» и гонки на игрушечных роботах-доставщиках.

📅 27 ноября в 17.00 (по Екб)
📍 Екатеринбург (креативный кластер «Домна») + онлайн

Регистрация на митап
1👍1🔥1
Когда вы создаете кастомный метод __repr__ для объекта, обычно нужно включить представление его атрибутов. Однако важно помнить, что нужно явно вызывать repr(), так как форматирование вызывает str() вместо repr().

Пример простого кода:


class Pair:
def __init__(self, left, right):
self.left = left
self.right = right

def __repr__(self):
class_name = type(self).__name__
repr_left = repr(self.left)
repr_right = repr(self.right)
return f'{class_name}({repr_left}, {repr_right})'


Проблема возникает, если вы вызываете repr для объекта, который содержит ссылку на самого себя. Это может привести к рекурсии:


In : p = Pair(1, 2)
In : p
Out: Pair(1, 2)
In : p.right = p
In : p
Out: [...]
RecursionError: maximum recursion depth exceeded while calling a Python object


Для решения этой проблемы можно использовать декоратор reprlib.recursive_repr, который обрабатывает рекурсивные вызовы:


@reprlib.recursive_repr()
def __repr__(self):
class_name = type(self).__name__
repr_left = repr(self.left)
repr_right = repr(self.right)
return f'{class_name}({repr_left}, {repr_right})'


Теперь код работает корректно:


In : p = Pair(1, 2)
In : p.right = p
In : p
Out: Pair(1, ...)


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1
Тесты могут требовать временные файлы или директории. Для этого отлично подойдет модуль tempfile.

Так как временные файлы обычно нужно удалять после использования, tempfile предоставляет как контекстный менеджер, так и простые функции:


import os
import tempfile

with tempfile.TemporaryDirectory() as dir_path:
open(os.path.join(dir_path, 'a'), 'w').close()
open(os.path.join(dir_path, 'b'), 'w').close()
open(os.path.join(dir_path, 'c'), 'w').close()

assert files_of(dir_path) == ['a', 'b', 'c']


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Нативные значения float в Python используют аппаратные возможности вашего компьютера, поэтому любое значение внутренне представлено в виде двоичной дроби.

Это означает, что в большинстве случаев вы работаете с приближениями, а не с точными значениями:


In : format(0.1, '.17f')
Out: '0.10000000000000001'


Модуль decimal позволяет использовать десятичную арифметику с произвольной точностью:


In : Decimal(1) / Decimal(3)
Out: Decimal('0.3333333333333333333333333333')


Однако и этого может быть недостаточно:


In [61]: Decimal(1) / Decimal(3) * Decimal(3) == Decimal(1)
Out[61]: False


Для точных вычислений можно использовать fractions, где любое число хранится в виде рационального:


In : Fraction(1) / Fraction(3) * Fraction(3) == Fraction(1)
Out: True


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

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2💩2
UTF-8 - это кодировка с переменной длиной. Один символ может быть закодирован с использованием одного, двух, трёх или четырёх байтов. Это означает, что нельзя начать чтение строки в кодировке UTF-8 с произвольного байта, так как это может случайно разрушить символ:


In : lion = 'Löwe'
In : lion.encode('utf-8')[2:]
Out: b'\xb6we'
In : lion.encode('utf-8')[2:].decode('utf-8')
...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb6 in position 0: invalid start byte


Также это означает, что для пропуска первых N символов строки их необходимо прочитать и декодировать. Рассчитать смещение заранее невозможно.

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


0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx


Как видно, байт является начальным байтом символа, если его вид не совпадает с 10xxxxxx. Такие байты называются продолжением символа (continuation bytes). Давайте пропустим их:


def cut_bytes(s, n):
result = s.encode('utf-8')[n:]
mask = int('11000000', 2)
conbyte = int('10000000', 2)
while result[0] and result[0] & mask == conbyte:
result = result[1:]

return result.decode('utf-8')


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


In : cut_bytes(lion, 2)
Out: 'we'
In : cut_bytes(lion, 1)
Out: 'öwe'


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Иногда вам нужно запустить блок кода с несколькими контекстными менеджерами. Например:


with open('f') as f:
with open('g') as g:
with open('h') as h:
pass


Начиная с Python 2.7 и 3.1, это можно сделать с помощью одного выражения with:


o = open
with o('f') as f, o('g') as g, o('h') as h:
pass


До этого можно было использовать функцию contextlib.nested:


with nested(o('f'), o('g'), o('h')) as (f, g, h):
pass


Однако в современных версиях Python эта функция устарела и вызывает предупреждение. Вместо неё рекомендуется использовать более продвинутый инструмент — contextlib.ExitStack. Он позволяет войти в любое количество контекстов в произвольное время, но гарантирует их корректное завершение:


from contextlib import ExitStack

with ExitStack() as stack:
f = stack.enter_context(o('f'))
g = stack.enter_context(o('g'))
other = [
stack.enter_context(o(filename))
for filename in filenames
]


Это особенно полезно, когда количество контекстных менеджеров неизвестно заранее.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1
Когда корутина asyncio хочет остановиться и взаимодействовать с циклом событий (event loop), она использует await obj (или yield from obj до Python 3.6). Объект obj должен быть другой корутиной, объектом asyncio.Future или любым пользовательским объектом, похожим на Future (любой объект, у которого определен метод __await__).


async def coroutine():
await another_coroutine()

async def another_coroutine():
future = asyncio.Future()
await future

loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine())


Когда корутина ожидает (await) другую корутину, вторая начинает выполняться вместо первой. Если она ожидает третью, то выполняется третья. Это продолжается до тех пор, пока какая-нибудь корутина не ожидает объект Future. Объект Future фактически возвращает значение, и тогда цикл событий (event loop) получает управление.

Какое значение возвращает Future? Оно возвращает сам себя. Можете ли вы напрямую использовать yield для Future? Нет, это внутренняя деталь, о которой вам обычно не нужно беспокоиться.


class Awaitable:
def __await__(self):
future = asyncio.Future()
yield future
# RuntimeError: yield was used
# instead of yield from in task

async def coroutine():
await Awaitable()

loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine())


Почему возникает эта ошибка? Как asyncio понимает, что это вы используете yield для Future, а не сам Future? Есть простая защита: Future устанавливает внутренний флаг перед тем, как вернуть управление.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2