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

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

РКН clck.ru/3Ko7Hq
Download Telegram
PEP 424 позволяет генераторам и другим итерируемым объектам, у которых нет заранее определённого точного размера, предоставлять «подсказку» о длине. Например, следующий генератор, скорее всего, вернёт примерно 50 элементов:


(x for x in range(100) if random() > 0.5)


Если вы пишете свой итерируемый объект и хотите добавить такую подсказку, определите метод __length_hint__. Если же длина известна точно, лучше использовать len.

Если вы используете итерируемый объект и хотите узнать его ожидаемую длину, применяйте функцию operator.length_hint.

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

Первый — __bases__; он возвращает непосредственных родителей класса:


class A:
pass

class B(A):
pass

class C(A):
pass

class D(B, C):
pass


print(D.__bases__)
# (<class '__main__.B'>, <class '__main__.C'>)


Второй —


class B
он возвращает кортеж со всеми классами, которые задействованы при разрешении методов (отсюда и название), то есть родителей, их родителей и так далее:


print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)


👉@BookPython
Класс объекта доступен через атрибут __class__:


>>> [1, 2].__class__
<class 'list'>


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


>>> type([1, 2])
<class 'list'>


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


>>> class A:
... pass
...
>>> class B(A):
... pass
...
>>> type(B())
<class '__main__.B'>
>>> isinstance(B(), A)
True


👉@BookPython
Оператор in можно использовать с генераторами: x in g. Python будет итерировать g, пока x не найдётся или генератор не исчерпается.


>>> def g():
... print(1)
... yield 1
... print(2)
... yield 2
... print(3)
... yield 3
...
>>> 2 in g()
1
2
True


Однако range() делает для вас больше. В нём переопределён магический метод __contains__, который позволяет оператору in работать с сложностью O(1):


In [1]: %timeit 10**20 in range(10**30)
375 ns ± 10.7 ns per loop


Учтите, что это не работает для функции xrange() в Python 2.

👉@BookPython
В Python операторы += и + являются разными. За их поведение отвечают методы __iadd__ и __add__ соответственно.


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

def __iadd__(self, another):
self.x += another.x
return self

def __add__(self, another):
return type(self)(self.x + another.x)


Если __iadd__ не определён, выражение a += b сводится к простому a = a + b.

Обычно разница между += и + в том, что первый изменяет объект, а второй создаёт новый:


>>> a = [1, 2, 3]
>>> b = a
>>> a += [4]
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>> a = a + [5]
>>> a
[1, 2, 3, 4, 5]
>>> b
[1, 2, 3, 4]


👉@BookPython
В Python блок else может располагаться не только после if, но и после for и while. Код внутри else выполняется, если цикл не был прерван оператором break.

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


>>> first_odd = None
>>> for x in [2, 3, 4, 5]:
... if x % 2 == 1:
... first_odd = x
... break
... else:
... raise ValueError('В списке нет нечетных элементов')
...
>>> first_odd
3



>>> for x in [2, 4, 6]:
... if x % 2 == 1:
... first_odd = x
... break
... else:
... raise ValueError('В списке нет нечетных элементов')
...
...
ValueError: В списке нет нечетных элементов


👉@BookPython
Иногда нужно выполнить блок кода с несколькими менеджерами контекста:


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


Если же число менеджеров контекста заранее неизвестно, лучше подойдёт более продвинутый инструмент. contextlib.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
]


👉@BookPython
Все объекты, которые в настоящий момент существуют в памяти интерпретатора, можно получить с помощью gc.get_objects():


In [1]: class A:
...: def __init__(self, x):
...: self._x = x
...:
...: def __repr__(self):
...: class_name = type(self).__name__
...: x = self._x
...: return f'{class_name}({x!r})'
...:

In [2]: A(1)
Out[2]: A(1)

In [3]: A(2)
Out[3]: A(2)

In [4]: A(3)
Out[4]: A(3)

In [5]: [x for x in gc.get_objects() if isinstance(x, A)]
Out[5]: [A(1), A(2), A(3)]


👉@BookPython
Начиная с Python 3.0, при возникновении нового исключения внутри блока except перехваченное исключение автоматически сохраняется в атрибуте __context__ создаваемого исключения. В результате при выводе будут показаны оба исключения:


try:
1 / 0
except ZeroDivisionError:
raise ValueError('Zero!')



(most recent call last):
File "test.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "test.py", line 4, in <module>
raise ValueError('Zero!')
ValueError: Zero!


Кроме того, вы можете явно указать причинное исключение, использовав конструкцию raise … from. Тогда в атрибут __cause__ нового исключения будет помещено исходное:


division_error = None

try:
1 / 0
except ZeroDivisionError as e:
division_error = e

raise ValueError('Zero!') from division_error



(most recent call last):
File "test.py", line 4, in <module>
1 / 0
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "test.py", line 8, in <module>
raise ValueError('Zero!') from division_error
ValueError: Zero!


👉@BookPython
In:


int('୧৬𝟙༣')


Out:


1613


Цифры 0 1 2 3 4 5 6 7 8 9 — это не единственные символы, которые считаются цифрами. Python следует правилам Unicode и считает «цифрами» сотни различных символов. Вот полный список таких символов.

Это влияет на такие функции, как int, unicode.isdecimal и даже re.match:


# Пример 1
int('෯')
# Вывод:
9

# Пример 2
'٢'.isdecimal()
# Вывод:
True

# Пример 3
import re
bool(re.match(r'\d', '౫'))
# Вывод:
True


👉@BookPython
>>> bool(datetime(2018, 1, 1).time())
False
>>> bool(datetime(2018, 1, 1, 13, 12, 11).time())
True


До Python 3.5 объекты datetime.time() считались ложными, если они представляли полночь по UTC. Это могло приводить к трудноуловимым ошибкам. В следующих примерах if not может выполниться не потому, что created_time равен None, а потому, что время — полночь.


def create(created_time=None) -> None:
if not created_time:
created_time = datetime.now().time()


Можно исправить это, явно проверяя на None:


def create(created_time=None) -> None:
if created_time is None:
created_time = datetime.now().time()


👉@BookPython
В Python нет поддержки асинхронных операций с файлами. Чтобы сделать их неблокирующими, нужно использовать отдельные потоки.

Для асинхронного выполнения кода в потоке следует использовать метод loop.run_in_executor.

Сторонний модуль aiofiles делает всё это за тебя, предоставляя простой и удобный интерфейс:


async with aiofiles.open('filename', mode='r') as f:
contents = await f.read()


👉@BookPython
Иногда нужно создать функцию на основе более универсальной.

Например, у функции int() есть параметр base, который мы хотим зафиксировать, чтобы получить новую функцию base2:


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


functools.partial позволяет сделать то же самое, но точнее и семантически понятнее:


base2 = partial(int, base=2)


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


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


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


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


👉@BookPython
Существует две встроенные функции, которые позволяют анализировать итерируемые объекты без необходимости писать тривиальные и избыточные циклы for. Это all и any.

any возвращает True, если хотя бы одно значение истинно; all возвращает True, только если все значения истинны. Для пустого итерируемого объекта all возвращает True, а anyFalse.

Обе функции особенно полезны в сочетании со списковыми включениями (list comprehensions):


package_broken = any(
part.is_broken() for part in package.get_parts()
)
package_ok = all(
part.ok() for part in package.get_parts()
)


Функции any и all зачастую взаимозаменяемы благодаря законам де Моргана. Используй ту, с которой код будет понятнее.

👉@BookPython
Если ты хочешь, чтобы у объектов класса автоматически увеличивался ID, это можно реализовать, отслеживая текущий ID в атрибуте класса:


class Task:
_task_id = 0

def __init__(self):
self._id = self._task_id
type(self)._task_id += 1


Обрати внимание, что нельзя использовать self._task_id += 1 — это создаст атрибут _task_id внутри экземпляра, а не изменит значение на уровне класса.

Лучше использовать фабричный метод вместо __init__, чтобы код выглядел аккуратнее:


class Task:
_task_id = 0

def __init__(self, task_id):
self._id = task_id

@classmethod
def create(cls):
obj = cls(cls._task_id)
cls._task_id += 1
return obj


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

👉@BookPython