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

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

РКН clck.ru/3Ko7Hq
Download Telegram
Есть шесть магических методов Python, которые определяют правила сравнения объектов:
__lt__ для <
__gt__ для >
__le__ для <=
__ge__ для >=
__eq__ для ==
__ne__ для !=

Если некоторые из этих методов не определены или возвращают NotImplemented, применяются следующие правила:
a.__lt__(b) эквивалентно b.__gt__(a)
a.__le__(b) эквивалентно b.__ge__(a)
a.__eq__(b) эквивалентно not a.__ne__(b) (обратите внимание, что в этом случае a и b не меняются местами)

Однако, из a >= b и a != b не следует автоматически, что a > b.
Декоратор functools.total_ordering создает все шесть методов на основе __eq__ и одного из следующих: __lt__, __gt__, __le__ или __ge__.


from functools import total_ordering

@total_ordering
class User:
def __init__(self, pk, name):
self.pk = pk
self.name = name

def __le__(self, other):
return self.pk <= other.pk

def __eq__(self, other):
return self.pk == other.pk

assert User(2, 'Vadim') < User(13, 'Catherine')


👉@BookPython
👍4
В Python можно переопределить оператор квадратных скобок ([]), реализовав магический метод __getitem__.
Вот так можно создать объект, который виртуально содержит бесконечное количество повторяющихся элементов:


class Cycle:
def __init__(self, lst):
self._lst = lst

def __getitem__(self, index):
return self._lst[index % len(self._lst)]

print(Cycle(['a', 'b', 'c'])[100]) # 'b'


Необычность здесь в том, что оператор [] поддерживает особый синтаксис. Его можно использовать не только так — [2], но и так — [2:10], [2:10:2], [2::2] или даже [:].
Обычно это интерпретируется как [start:stop:step], но вы можете задать любую логику для своих объектов.

Что же передаётся в параметр index метода __getitem__, если использовать такой синтаксис? Для этого в Python существуют объекты slice.


class Inspector:
def __getitem__(self, index):
print(index)

Inspector()[1]
# 1

Inspector()[1:2]
# slice(1, 2, None)

Inspector()[1:2:3]
# slice(1, 2, 3)

Inspector()[:]
# slice(None, None, None)


Можно даже комбинировать кортежи и срезы:


Inspector()[:, 0, :]
# (slice(None, None, None), 0, slice(None, None, None))


Объект slice сам по себе ничего не делает — он просто хранит атрибуты start, stop и step:


s = slice(1, 2, 3)
s.start # 1
s.stop # 2
s.step # 3


👉@BookPython
👍5
Оператор 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
👍1
Оператор 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
👍41
Некоторый код, который вы используете, может выводить данные, представляющие для вас интерес, в stdout, вместо того чтобы предоставлять какой-то API, пригодный для использования в программе (например, возвращать строку).

Вместо того чтобы рефакторить такой код, вы можете использовать менеджер контекста contextlib.redirect_stdout, который позволяет временно перенаправить stdout в любой объект, похожий на файл. В связке с io.StringIO это позволяет захватывать вывод в переменную.


from contextlib import redirect_stdout
from io import StringIO

s = StringIO()
with redirect_stdout(s):
print(42)

print(s.getvalue())


Также доступен contextlib.redirect_stderr для перенаправления sys.stderr.

👉@BookPython
👍2🤔2
Символ \ в обычной строке имеет специальное значение.
\t — это символ табуляции, \r — возврат каретки и так далее.

Можно использовать сырые строки (raw-strings), чтобы отключить это поведение.
r'\t' — это просто обратный слэш и буква t.

Очевидно, что нельзя использовать ' внутри r'...'. Однако его всё ещё можно экранировать через \, но при этом сам \ сохраняется в строке:


>>> print(r'It\'s insane!')
It\'s insane!


👉@BookPython
👍1
В списковых включениях (list comprehensions) может быть больше одного цикла for и условия if:


In : [(x, y) for x in range(3) for y in range(3)]
Out: [
(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)
]



In : [
(x, y)
for x in range(3)
for y in range(3)
if x != 0
if y != 0
]
Out: [(1, 1), (1, 2), (2, 1), (2, 2)]


Кроме того, любое выражение внутри for и if может использовать все переменные, которые были определены ранее:


In : [
(x, y)
for x in range(3)
for y in range(x + 2)
if x != y
]
Out: [
(0, 1),
(1, 0), (1, 2),
(2, 0), (2, 1), (2, 3)
]


Можно смешивать if и for в любом порядке:


In : [
(x, y)
for x in range(5)
if x % 2
for y in range(x + 2)
if x != y
]
Out: [
(1, 0), (1, 2),
(3, 0), (3, 1), (3, 2), (3, 4)
]


👉@BookPython
👍6👎21
Начиная с Python 3.5, стало возможно использовать распаковку в литералах словарей и списков.

Пример:


In : {**{'a': 1}, 'b': 2, **{'c': 3}}
Out: {'a': 1, 'b': 2, 'c': 3}



In : [1, 2, *[3, 4]]
Out: [1, 2, 3, 4]


Для словарей эта форма ещё более мощная, чем функция dict, так как позволяет переопределять значения:


In : {**{'a': 1, 'b': 1}, 'a': 2, **{'b': 3}}
Out: {'a': 2, 'b': 3}


👉@BookPython
👍31
В Python функция range() определяет все целые числа в полуоткрытом интервале.
То есть range(2, 10) математически означает [2, 10),
или, говоря на языке Python: [2, 3, 4, 5, 6, 7, 8, 9].

Несмотря на асимметрию, это не ошибка и не случайность.
В этом есть логика: такой подход позволяет "склеивать" два соседних интервала без риска ошибиться на единицу:

[a, c) = [a, b) + [b, c)

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

[a, c] = [a, b] + [b+1, c]

Эта же идея объясняет, почему индексация начинается с нуля:
[0, N) содержит ровно N элементов.

Эдсгер Дейкстра написал на эту тему отличную статью ещё в 1982 году.

👉@BookPython
👍1321
itertools.tee() создаёт несколько итераторов из одного. Это может быть полезно, если нескольким потребителям нужно читать один и тот же поток.

Пример:


In : a, b, c = tee(iter(input, ''), 3)

In : next(a), next(c)
FIRST
Out: ('FIRST', 'FIRST')

In : next(a), next(b)
SECOND
Out: ('SECOND', 'FIRST')

In : next(a), next(b), next(c)
THIRD
Out: ('THIRD', 'SECOND', 'SECOND')


Данные, которые ещё не были использованы всеми итераторами, сохраняются в памяти. Если часть созданных итераторов ещё не была начата в тот момент, когда другой уже дошёл до конца, это означает, что все сгенерированные элементы будут храниться в памяти для будущего использования.
В таком случае проще и эффективнее использовать list(iter(input, '')), чем tee.

👉@BookPython
👍2
Условное использование менеджеров контекста обычно доставляет неудобства: нельзя просто разместить with внутри блока if, не заключив туда весь блок with. Это часто приводит к дублированию кода:


def print_whole_file(
*,
path: Optional[str] = None,
file_obj: Optional[TextIO] = None
):
assert path or file_obj

if path:
with open(path) as f:
print(f.read(), end='')
else:
print(file_obj.read(), end='')


Способ борьбы с этой проблемой — использовать ExitStack и вызывать enter_context внутри if:


def print_whole_file(
*,
path: Optional[str] = None,
file_obj: Optional[TextIO] = None
):
assert path or file_obj

with ExitStack() as stack:
if path:
file_obj = stack.enter_context(
open(path)
)

print(file_obj.read(), end='')


Однако более очевидный способ достичь того же — использовать тривиальные менеджеры контекста, которые ничего не делают, когда они не нужны, вместо «настоящих». Начиная с Python 3.7, их можно получить с помощью contextlib.nullcontext:


def print_whole_file(
*,
path: Optional[str] = None,
file_obj: Optional[TextIO] = None
):
assert path or file_obj

if path:
context = open(path)
else:
context = nullcontext(file_obj)

with context as f:
print(f.read(), end='')


👉@BookPython
👍42
Любая выполняющаяся корутина asyncio может быть отменена с помощью метода cancel(). В корутину будет выброшено исключение CancelledError, что приведёт к её завершению, а также завершению всех оборачивающих её корутин, если только ошибка не будет перехвачена и подавлена.

CancelledError является подклассом Exception, а значит, его можно случайно перехватить конструкцией try ... except Exception, которая предназначена для отлова «любых ошибок». Чтобы безопасно это обработать внутри корутины, приходится писать примерно так:


try:
await action()
except asyncio.CancelledError:
raise
except Exception:
logging.exception('action failed')


👉@BookPython
👍21
Ты не можешь изменять переменные замыкания простым присваиванием.
Python рассматривает присваивание как определение локальной переменной внутри тела функции и вообще не делает замыкания.

Работает нормально, печатает 2:


def make_closure(x):
def closure():
print(x)

return closure

make_closure(2)()


Вызывает UnboundLocalError: local variable 'x' referenced before assignment:


def make_closure(x):
def closure():
print(x)
x *= 2
print(x)

return closure

make_closure(2)()


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


def make_closure(x):
def closure():
nonlocal x
print(x)
x *= 2
print(x)

return closure

make_closure(2)()


👉@BookPython
👍311
Декоратор создаёт новый объект (обычно функцию), используя в качестве аргумента другую единственную функцию. Однако иногда хочется задать больше, чем одну функцию.

Сделать это напрямую невозможно из-за ограничений синтаксиса Python, но можно использовать небольшой трюк. Возвращаемая функция может содержать ещё один декоратор, который можно повторно применить к дополнительным функциям, добавляя новое поведение. Примерно так работает @property:


@property
def x(self):
return self._x

@x.setter
def x(self, value):
self._x = value


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


from functools import wraps

def make_case_decorator(func):
def case_decorator(*case_decorator_args):
def decorator(special_case_func):
@wraps(func)
def decorated(*args):
if case_decorator_args == args:
return special_case_func(*args)
return func(*args)

decorated.case = make_case_decorator(decorated)

return decorated

return decorator

return case_decorator


def special_cases(func):
@wraps(func)
def decorated(*args):
return func(*args)

decorated.case = make_case_decorator(decorated)

return decorated


@special_cases
def fact(x):
return x * fact(x - 1)

@fact.case(0)
def fact(x):
return 1

@fact.case(10)
def fact(x):
print(f'(сработала оптимизация для {x})')
return 3628800


👉@BookPython
👍31
Если вы хотите измерить время между двумя событиями, следует использовать time.monotonic() вместо time.time().
time.monotonic() никогда не идёт назад, даже если системные часы были изменены:


from contextlib import contextmanager
import time


@contextmanager
def timeit():
start = time.monotonic()
yield
print(time.monotonic() - start)

def main():
with timeit():
time.sleep(2)

main()


👉@BookPython
👍51
Проблема при вызове repr для других объектов внутри собственного метода __repr__ заключается в том, что нельзя гарантировать, что ни один из этих объектов не равен self, и вызов не окажется рекурсивным:


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__
return f'{class_name}({self.left!r}, {self.right!r})'


Теперь всё работает корректно:


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


👉@BookPython
👍1
Словари, которые используются для хранения атрибутов объектов, не такие же, как те, что вы создаёте с помощью dict, хотя выглядят они абсолютно одинаково:


>>> from sys import getsizeof
>>> class A:
... pass
...
>>> a = dict()
>>> b = A().__dict__
>>> type(a)
<class 'dict'>
>>> type(b)
<class 'dict'>
>>> a
{}
>>> b
{}
>>> getsizeof(a)
240
>>> getsizeof(b)
112


Чтобы уменьшить потребление памяти, словари для __dict__ реализованы иначе. Они разделяют ключи между всеми экземплярами класса A. Однако важно понимать, что b на самом деле не меньше, чем a, - это просто особенность работы getsizeof.

👉@BookPython
4👍1
Функции, объявленные в теле класса, не видят область видимости класса. Это логично, так как область видимости класса существует только во время создания класса.


>>> class A:
... x = 2
... def f():
... print(x)
... f()
...
[...]
NameError: name 'x' is not defined


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


>>> class A:
... x = 2
... def f(self):
... print(self.x)
...
>>> A().f()
2


Что немного неожиданно — то же самое верно и для генераторов и списковых включений (comprehensions).
Они имеют свою собственную область видимости и не могут обращаться к области видимости класса.

Это особенно логично для генераторов, так как они выполняются уже после того, как создание класса завершено.


>>> class A:
... x = 2
... y = [x for _ in range(5)]
...
[...]
NameError: name 'x' is not defined


Comprehensions при этом не имеют доступа к self.
Единственный способ заставить это работать - добавить ещё один уровень области видимости, например, через lambda (да, это выглядит не слишком красиво):


>>> class A:
... x = 2
... y = (lambda x=x: [x for _ in range(5)])()
...
>>> A.y
[2, 2, 2, 2, 2]


👉@BookPython
👍2
У Python очень короткий список встроенных констант. Одна из них - Ellipsis, которую также можно записать как .... Эта константа не имеет особого значения для интерпретатора, но используется в тех местах, где подобный синтаксис уместен.

numpy поддерживает Ellipsis как аргумент для __getitem__. Например, x[...] возвращает все элементы массива x.

PEP 484 задаёт дополнительный смысл: Callable[..., type] — способ определить тип вызываемых объектов без указания типов аргументов.

Наконец, ... можно использовать, чтобы показать, что функция ещё не реализована. Это полностью корректный Python-код:


def x():
...


👉@BookPython
👍3
🚀 Подборка Telegram каналов для программистов

Системное администрирование, DevOps 📌

https://t.me/bash_srv Bash Советы
https://t.me/win_sysadmin Системный Администратор Windows
https://t.me/sysadmin_girl Девочка Сисадмин
https://t.me/srv_admin_linux Админские угодья
https://t.me/linux_srv Типичный Сисадмин
https://t.me/devopslib Библиотека девопса | DevOps, SRE, Sysadmin
https://t.me/linux_odmin Linux: Системный администратор
https://t.me/devops_star DevOps Star (Звезда Девопса)
https://t.me/i_linux Системный администратор
https://t.me/linuxchmod Linux
https://t.me/sys_adminos Системный Администратор
https://t.me/tipsysdmin Типичный Сисадмин (фото железа, было/стало)
https://t.me/sysadminof Книги для админов, полезные материалы
https://t.me/i_odmin Все для системного администратора
https://t.me/i_odmin_book Библиотека Системного Администратора
https://t.me/i_odmin_chat Чат системных администраторов
https://t.me/i_DevOps DevOps: Пишем о Docker, Kubernetes и др.
https://t.me/sysadminoff Новости Линукс Linux

1C разработка 📌
https://t.me/odin1C_rus Cтатьи, курсы, советы, шаблоны кода 1С
https://t.me/DevLab1C 1С:Предприятие 8
https://t.me/razrab_1C 1C Разработчик
https://t.me/buh1C_prog 1C Программист | Бухгалтерия и Учёт
https://t.me/rabota1C_rus Вакансии для программистов 1С

Программирование C++📌
https://t.me/cpp_lib Библиотека C/C++ разработчика
https://t.me/cpp_knigi Книги для программистов C/C++
https://t.me/cpp_geek Учим C/C++ на примерах

Программирование Python 📌
https://t.me/pythonofff Python академия.
https://t.me/BookPython Библиотека Python разработчика
https://t.me/python_real Python подборки на русском и английском
https://t.me/python_360 Книги по Python

Java разработка 📌
https://t.me/BookJava Библиотека Java разработчика
https://t.me/java_360 Книги по Java Rus
https://t.me/java_geek Учим Java на примерах

GitHub Сообщество 📌
https://t.me/Githublib Интересное из GitHub

Базы данных (Data Base) 📌
https://t.me/database_info Все про базы данных

Мобильная разработка: iOS, Android 📌
https://t.me/developer_mobila Мобильная разработка
https://t.me/kotlin_lib Подборки полезного материала по Kotlin

Фронтенд разработка 📌
https://t.me/frontend_1 Подборки для frontend разработчиков
https://t.me/frontend_sovet Frontend советы, примеры и практика!
https://t.me/React_lib Подборки по React js и все что с ним связано

Разработка игр 📌
https://t.me/game_devv Все о разработке игр

Библиотеки 📌
https://t.me/book_for_dev Книги для программистов Rus
https://t.me/programmist_of Книги по программированию
https://t.me/proglb Библиотека программиста
https://t.me/bfbook Книги для программистов

БигДата, машинное обучение 📌
https://t.me/bigdata_1 Big Data, Machine Learning

Программирование 📌
https://t.me/bookflow Лекции, видеоуроки, доклады с IT конференций
https://t.me/rust_lib Полезный контент по программированию на Rust
https://t.me/golang_lib Библиотека Go (Golang) разработчика
https://t.me/itmozg Программисты, дизайнеры, новости из мира IT
https://t.me/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻
https://t.me/nodejs_lib Подборки по Node js и все что с ним связано
https://t.me/ruby_lib Библиотека Ruby программиста
https://t.me/lifeproger Жизнь программиста. Авторский канал.

QA, тестирование 📌
https://t.me/testlab_qa Библиотека тестировщика

Шутки программистов 📌
https://t.me/itumor Шутки программистов

Защита, взлом, безопасность 📌
https://t.me/thehaking Канал о кибербезопасности
https://t.me/xakep_2 Хакер Free

Книги, статьи для дизайнеров 📌
https://t.me/ux_web Статьи, книги для дизайнеров

Математика 📌
https://t.me/Pomatematike Канал по математике
https://t.me/phis_mat Обучающие видео, книги по Физике и Математике
https://t.me/matgeoru Математика | Геометрия | Логика

Excel лайфхак📌
https://t.me/Excel_lifehack

https://t.me/mir_teh Мир технологий (Technology World)

Вакансии 📌
https://t.me/sysadmin_rabota Системный Администратор
https://t.me/progjob Вакансии в IT
1
В Python разные структуры данных объединяются разными способами.

Списки используют оператор +:


>>> [1, 2] + [2, 3]
[1, 2, 2, 3]


Кортежи и строки также используют +:


>>> (1, 2) + (2, 3)
(1, 2, 2, 3)

>>> "12" + "23"
'1223'


Deque (двусторонняя очередь) тоже поддерживает +:


>>> deque([1, 2]) + deque([2, 3])
deque([1, 2, 2, 3])


Множества объединяются с помощью оператора |:


>>> {1, 2} | {2, 3}
{1, 2, 3}


Словари объединяются по-другому, и порядок важен, если ключи пересекаются:


>>> {**dict(a=1, b=2), **dict(b=3, c=4)}
{'a': 1, 'b': 3, 'c': 4}

>>> {**dict(b=3, c=4), **dict(a=1, b=2)}
{'b': 2, 'c': 4, 'a': 1}


Counter (счётчик) можно сложить с помощью +, при этом значения суммируются:


>>> Counter(dict(a=1, b=2)) + Counter(dict(b=3, c=4))
Counter({'b': 5, 'c': 4, 'a': 1})


👉@BookPython
👍4