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

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

РКН clck.ru/3Ko7Hq
Download Telegram
Как упростить работу с аргументами в командной строке с помощью typer

Раньше для CLI-приложений на Python я использовал argparse, потом был click, но недавно полностью перешёл на typer. Это библиотека от автора FastAPI, и она реально 🔥

Вот простой пример:


import typer

app = typer.Typer()

@app.command()
def hello(name: str, age: int = 18):
print(f"Привет, {name}! Тебе {age} лет.")

if __name__ == "__main__":
app()


Теперь можно запускать в терминале:

$ python main.py hello Alice --age 30
Привет, Alice! Тебе 30 лет.


Что круто:
- Автоматически генерируется --help
- Пишется почти как обычная функция
- Есть автокомплит в оболочках (bash/zsh)
- Поддержка аннотаций типов и валидации "из коробки"

Если ты всё ещё страдаешь с argparse, рекомендую попробовать typer. Особенно если ты уже кайфуешь от FastAPI — синтаксис и подход очень похожи.

👉@BookPython
Создание объекта в Python включает два ключевых этапа. Сначала вызывается метод __new__, который создаёт и возвращает новый объект. Затем вызывается метод __init__ для инициализации состояния этого объекта.

Однако, если __new__ возвращает объект, который не является экземпляром исходного класса, метод __init__ не будет вызван. Это связано с тем, что возвращаемый объект, вероятно, уже создан другим классом, и его __init__ уже был выполнен:


class Foo:
def __new__(cls, x):
return dict(x=x)

def __init__(self, x):
print(x) # Никогда не вызывается

print(Foo(0))


Важно: не следует создавать экземпляры того же класса в __new__ с использованием обычного конструктора (Foo(...)). Это может привести к двойному вызову __init__ или даже к бесконечной рекурсии.

Пример бесконечной рекурсии:


class Foo:
def __new__(cls, x):
return Foo(-x) # Рекурсия


Пример двойного вызова __init__:


class Foo:
def __new__(cls, x):
if x < 0:
return Foo(-x)
return super().__new__(cls)

def __init__(self, x):
print(x)
self._x = x


Правильный способ:


class Foo:
def __new__(cls, x):
if x < 0:
return cls.__new__(cls, -x)
return super().__new__(cls)

def __init__(self, x):
print(x)
self._x = x


👉@BookPython
Встроенные значения float в Python используют оборудование вашего компьютера напрямую, поэтому любое значение представляется внутренне в виде двоичной дроби.

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


>>> format(0.1, '.17f')
'0.10000000000000001'


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


>>> from decimal import Decimal
>>> Decimal(1) / Decimal(3)
Decimal('0.3333333333333333333333333333')


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


>>> Decimal(1) / Decimal(3) * Decimal(3) == Decimal(1)
False


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


>>> from fractions import Fraction
>>> Fraction(1) / Fraction(3) * Fraction(3) == Fraction(1)
True


Очевидное ограничение — всё равно приходится использовать приближения для иррациональных чисел, таких как π.

👉@BookPython
💡 Как избежать повторения кода с помощью functools.partial

Как упростить код и избежать дублирования с помощью functools.partial.

Допустим, у нас есть функция send_email(to, subject, body, is_html=False), и мы часто вызываем её с одним и тем же параметром is_html=True.

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


from functools import partial

send_html_email = partial(send_email, is_html=True)

# Теперь можно вызывать проще:
send_html_email("user@example.com", "Привет", "<b>Как дела?</b>")


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

* логгеры с предустановленным уровнем
* коннекторы с общими параметрами
* команды CLI с типовыми флагами

Таким образом, вы уменьшаете дублирование и делаете код читаемее. А ещё это красивый способ внедрить DI без фреймворков — просто передайте partial.

👉@BookPython
Самый простой способ использовать модуль logging — вызывать функции напрямую, без создания объекта логгера:


import logging
logging.error('xxx')


Этот глобальный логгер можно настроить с помощью вызова logging.basicConfig():


import logging
logging.basicConfig(format='-- %(message)s --')
logging.error('xxx') # -- xxx --


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


import logging
logging.error('xxx') # ERROR:root:xxx
logging.basicConfig(format='-- %(message)s --')
logging.error('xxx') # ERROR:root:xxx


👉@BookPython
🚀 Как логировать без боли в Python

Как настроить логирование в Python один раз — и больше к этому не возвращаться.

Обычно начинающие разработчики либо используют print(), либо подключают logging, но каждый раз пишут кучу однотипного кода. Я так тоже делал. Но потом вывел себе простую универсальную схему, которую теперь кидаю в каждый новый проект:


import logging

def setup_logger(name: str) -> logging.Logger:
logger = logging.getLogger(name)
if not logger.hasHandlers():
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(name)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger

logger = setup_logger(__name__)

logger.info("Скрипт стартовал")


Что мы получаем:

* Удобный формат времени и уровня лога
* Защиту от дублирования логов (если модуль импортируется несколько раз)
* Готовность к масштабированию (можно легко добавить файл-логгер)

Если вы устали от print(), просто сохраните себе этот сниппет — он сэкономит вам время и нервы.

Пользуетесь ли вы встроенным logging, или предпочитаете что-то вроде loguru?

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


$ cat test/foo.py
print(__file__)
$ python test/foo.py
test/foo.py


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

Чтобы получить абсолютный путь из относительного, можно использовать os.path.abspath. Поэтому распространённый приём для получения пути к директории скрипта выглядит так:


import os

dir_path = os.path.dirname(
os.path.abspath(__file__)
)


👉@BookPython
Существует два понятия с похожими названиями, которые легко перепутать: переопределение (overriding) и перегрузка (overloading).

Переопределение происходит, когда дочерний класс определяет метод, который уже был реализован в родительском классе, фактически заменяя его. В некоторых языках необходимо явно указывать, что метод переопределяется (например, в C# используется модификатор override), в других — это необязательно (в Java можно, но не обязательно использовать аннотацию @Override). В Python нет ни обязательного, ни стандартного способа обозначать такие методы (некоторые программисты применяют пользовательский декоратор @override, который ничего не делает, а служит только для читаемости кода).

Перегрузка, напротив, — это наличие нескольких функций с одним и тем же именем, но разными сигнатурами. Это поддерживается в таких языках, как Java и C++, и часто используется как способ предоставления значений по умолчанию:


class Foo {
public static void main(String[] args) {
System.out.println(Hello());
}

public static String Hello() {
return Hello("world");
}

public static String Hello(String name) {
return "Hello, " + name;
}
}


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


def quadrilateral_area(*args):
if len(args) == 4:
quadrilateral = Quadrilateral(*args)
elif len(args) == 1:
quadrilateral = args[0]
else:
raise TypeError()

return quadrilateral.area()


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


from typing import overload

@overload
def quadrilateral_area(
q: Quadrilateral
) -> float: ...

@overload
def quadrilateral_area(
p1: Point, p2: Point,
p3: Point, p4: Point
) -> float: ...


👉@BookPython
В 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'


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

Что же передаётся в __getitem__ в таких случаях? Объекты 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)
print(s.start) # 1
print(s.stop) # 2
print(s.step) # 3


👉@BookPython
🚀 Подборка 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
Иногда возникает необходимость выполнить участок кода и проигнорировать все возможные исключения. Это оправдано в случае с плагинами, сторонними модулями и другими компонентами, устройство которых вам неизвестно или которым вы не доверяете.

Правильный способ сделать это — использовать конструкцию try с except Exception, а не голый except:


try:
foreign()
except Exception:
logging.warn('fail', exc_info=True)


Голый except эквивалентен except BaseException. А разница между BaseException и Exception в том, что BaseException включает исключения, которые, как правило, ловить не следует, например, KeyboardInterrupt.

👉@BookPython
В Python имя переменной может состоять из одного символа подчёркивания: _. Хотя такие имена обычно недостаточно описательны и их не стоит использовать, существует по крайней мере три случая, когда _ имеет общепринятое значение.

Во-первых, в интерактивных интерпретаторах Python _ используется для хранения результата последнего выполненного выражения:


>>> 2 + 2
4
>>> _
4


Во-вторых, в документации модуля gettext рекомендуется создавать псевдоним для функции gettext() в виде _(), чтобы не загромождать код.

В-третьих, _ используется, когда необходимо придумать имя для значения, которое не представляет интереса:


>>> log_entry = '10:50:24 14234 GET /api/v1/test'
>>> time, _, method, location = log_entry.split()


👉@BookPython
Если вы импортируете модуль, который уже был импортирован, ничего не произойдёт, так как Python отслеживает, какие модули уже были загружены. Все такие модули помещаются в словарь sys.modules:


In : import sys
In : 'sys' in sys.modules.keys()
Out: True


Если вам действительно нужно перезагрузить модуль, следует использовать функцию importlib.reload(m). Здесь m — это объект модуля, который был успешно импортирован ранее, а не строка с его именем:


In : import importlib
In : importlib.reload(importlib)
Out[5]: <module 'importlib' from '/home/bookpython/.ve/pythonetc/lib/python3.6/importlib/__init__.py'>


👉@BookPython
Некоторые модули могут содержать такие запутанные конструкции:


try:
cache
except NameError:
cache = {}


На первый взгляд, в этом нет смысла: cache однозначно вызовет NameError в начале модуля, так как переменной ранее не присваивалось значение.

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

👉@BookPython
Каждая хорошо спроектированная консольная утилита должна принимать аргументы в виде опций (например, -h или --help), опций с параметрами (--log-level 2) или позиционных параметров (cp file1 file2).

Опции отличаются от позиционных параметров тем, что начинаются с одного или двух дефисов. Проблемы возникают, когда позиционные аргументы тоже начинаются с дефиса — например, если нужно удалить файл с именем -rf: команда rm -rf в таком случае не работает как ожидается.

Общепринятый способ решения этой проблемы — поддержка разделителя --. Аргументы, идущие после --, никогда не интерпретируются как опции:


$ echo test > -rf
$ cat -rf
cat: invalid option -- 'r'
Try 'cat --help' for more information.
$ cat -- -rf
test
$ rm -- -rf
$ cat -- -rf
cat: -rf: No such file or directory


Модуль argparse автоматически обрабатывает -- за тебя.

👉@BookPython
Есть три ситуации, в которых только что созданную переменную нельзя аннотировать типом: распаковка кортежей, циклы for и инструкции with.

Все эти примеры некорректны:


name: str, age: int = student

for x: int in numbers:
...

with connection() as conn: Connection:
...


Правильный способ указать тип таких переменных — объявить их заранее, без инициализации:


conn: Connection
with connection() as conn:
...


👉@BookPython
Многие Python-классы начинаются с похожего шаблона: простой конструктор, тривиальный __repr__ и прочие подобные вещи:


class Server:
def __init__(self, ip, version=4):
self.ip = ip
self._version = version

def __repr__(self):
return '{klass}("{ip!r}", {version!r})'.format(
klass=type(self).__name__,
ip=self.ip,
version=self._version,
)


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


class Server:
ip = attrib()
_version = attrib(default=4)

server = Server(ip='192.168.0.0.1', version=4)


Этот подход не только создаёт конструктор (__init__) и представление (__repr__), но и полный набор методов сравнения (__eq__, __lt__ и т. д.).

Кроме того, в Python 3.7 появилась стандартная альтернатива — data classes (датаклассы), которые решают ту же задачу (и даже больше). Они используют аннотации переменных — ещё одну относительно новую функцию Python. Вот пример:


@dataclass
class InventoryItem:
name: str
unit_price: float
quantity_on_hand: int = 0

def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand


Таким образом, dataclass тоже автоматически создаёт __init__, __repr__, методы сравнения и многое другое, основываясь лишь на аннотациях типов.

👉@BookPython
В Python сортировка по умолчанию является стабильной, то есть сохраняет порядок равных элементов:


a = [2, -1, 0, 1, -2]
sorted(a, key=lambda x: x**2)
# [0, -1, 1, 2, -2]


Функции max и min тоже стараются быть согласованными с поведением sorted.
max работает аналогично sorted(a, reverse=True)[0], а min — как sorted(a)[0].
Это означает, что обе функции возвращают самый левый возможный результат:


max([2, -2], key=lambda x: x**2)
# 2

max([-2, 2], key=lambda x: x**2)
# -2

min([2, -2], key=lambda x: x**2)
# 2

min([-2, 2], key=lambda x: x**2)
# -2


👉@BookPython
Модуль collections предоставляет класс ChainMap, который позволяет использовать несколько отображений (словарей) как одно объединённое:


from collections import ChainMap

d = ChainMap(dict(a=1), dict(a=2, b=2))
d['a'] # 1
d['b'] # 2
d['c'] # ...
# KeyError: 'c'


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


d = ChainMap(dict(a=1), dict(a=2, b=2))
d['c'] = 3
d
# ChainMap({'a': 1, 'c': 3}, {'a': 2, 'b': 2})


👉@BookPython
🔍Тестовое собеседование на Middle Python-разработчика в четверг

22 мая(в четверг) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Python-разработчика.

Собес проведет Вадим Пуштаев, ex. head of backend в 💙, автор канала @pythonetc, архитектор в европейской компании

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

Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Python-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.

Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_py_bot

Реклама. ООО "ШОРТКАТ", ИНН: 9731139396, erid: 2VtzqwKtUmr
Please open Telegram to view this post
VIEW IN TELEGRAM