Библиотека Python разработчика | Книги по питону
18.6K subscribers
1.06K photos
403 videos
82 files
1.1K links
Погружение в CPython и архитектуру. Разбираем неочевидное поведение (GIL, Memory), Best Practices (SOLID, DDD) и тонкости Django/FastAPI. Решаем задачи с подвохом и оптимизируем алгоритмы. 🐍

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

РКН clck.ru/3Ko7Hq
Download Telegram
Сегодня я покажу вам простой, но очень полезный приём, который часто выручает при работе с Python-скриптами — автоматическое логирование вызовов функций с помощью декоратора.

Иногда, особенно в отладке, хочется видеть, какие функции вызываются, с какими аргументами и что они возвращают. Не писать же в каждую вручную print()? Вот тут и приходит на помощь наш герой — универсальный логгер-декоратор:


import functools

def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[CALL] {func.__name__} args={args} kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"[RETURN] {func.__name__} -> {result}")
return result
return wrapper


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


@log_calls
def multiply(a, b):
return a * b

multiply(3, 5)


📌 Вывод:

[CALL] multiply args=(3, 5) kwargs={}
[RETURN] multiply -> 15


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

Кстати, с небольшими изменениями можно направить вывод не в print(), а в logging, или даже сохранять в файл — по вкусу.

Пользуетесь такими декораторами? Или у вас свой лайфхак?

📲 Мы в MAX

👉@BookPython
👍1
Одна и та же строка может быть представлена по-разному в Unicode, и стандарт это учитывает. Он определяет два типа эквивалентности: последовательности могут быть канонически эквивалентными или совместимыми.

Канонически эквивалентные последовательности выглядят одинаково, но содержат разные кодовые точки. Например, символ ö может быть представлен как LATIN SMALL LETTER O WITH DIAERESIS (U+00F6) или как комбинация из o и диакритического знака: LATIN SMALL LETTER O (U+006F) + COMBINING DIAERESIS (U+0308).

Совместимые последовательности выглядят по-разному, но могут трактоваться одинаково с точки зрения смысла, например, ff и ff.

Для каждого из этих типов эквивалентности можно нормализовать строку в Unicode, сжимая или расширяя последовательности. В Python для этого используется модуль unicodedata:


import unicodedata

modes = [
# Сжать канонически эквивалентные
'NFC',
# Расширить канонически эквивалентные
'NFD',
# Сжать совместимые
'NFKC',
# Расширить совместимые
'NFKD',
]

s = 'ff + ö'

for mode in modes:
norm = unicodedata.normalize(mode, s)
print('\t'.join([
mode,
norm,
str(len(norm.encode('utf8'))),
]))


Результат:

NFC ff + ö 8
NFD ff + ö 9
NFKC ff + ö 7
NFKD ff + ö 8


📲 Мы в MAX

👉@BookPython
👍2🤯1
Обычно вы взаимодействуете с генератором, запрашивая данные с помощью next(gen). В Python 3 вы также можете отправлять значения обратно в генератор с помощью g.send(x). Но существует техника, которой вы, вероятно, не пользуетесь каждый день, а возможно, и вовсе не знаете: выбрасывание исключений внутри генератора.

С помощью gen.throw(e) можно выбросить исключение в той точке, где генератор gen приостановлен — то есть на инструкции yield. Если генератор обрабатывает это исключение, gen.throw(e) возвращает следующее значение, полученное через yield (или выбрасывает StopIteration, если генератор завершён). Если генератор не перехватывает исключение, оно пробрасывается обратно к вызывающему коду.


def gen():
try:
yield 1
except ValueError:
yield 2

g = gen()

next(g)
# Out: 1

g.throw(ValueError)
# Out: 2

g.throw(RuntimeError('TEST'))
# RuntimeError: TEST


Эта техника позволяет более точно управлять поведением генератора — не только передавать данные внутрь, но и, например, сообщать о проблемах со значениями, полученными через yield. Однако такие случаи бывают редко, и встретить g.throw в дикой природе почти невозможно.

Тем не менее, декоратор @contextmanager из модуля contextlib использует именно такую технику, позволяя коду внутри контекста перехватывать исключения.


from contextlib import contextmanager

@contextmanager
def atomic():
print('BEGIN')

try:
yield
except Exception:
print('ROLLBACK')
else:
print('COMMIT')

with atomic():
print('ERROR')
raise RuntimeError()



BEGIN
ERROR
ROLLBACK


📲 Мы в MAX

👉@BookPython
2👍1
Стандартный механизм расширения путей в оболочке называется globbing. Шаблоны, которые вы используете для сопоставления путей, называются globs.


$ echo /li*
/lib /lib64


Python поддерживает globbing с помощью модуля glob. Однако есть важное замечание: оболочка возвращает сам шаблон, если файлы не найдены, а Python — нет:


$ echo /zz**
/zz**
$ python -c 'from glob import glob; print(glob("/zz**"))'
[]


📲 Мы в MAX

👉@BookPython
4👍1
Функция super() позволяет обращаться к родительскому (базовому) классу. Это может быть очень полезно в случаях, когда производный класс хочет добавить что-то к реализации метода, а не полностью переопределять его:


class BaseTestCase(TestCase):
def setUp(self):
self._db = create_db()

class UserTestCase(BaseTestCase):
def setUp(self):
super().setUp()
self._user = create_user()


Имя функции super не означает "отличный" или "очень хороший". В данном контексте слово super означает "выше" (как, например, в слове superintendent — заведующий). Несмотря на это, super() не всегда ссылается на базовый класс — он может вернуть и "соседний" класс. Более точным названием была бы, возможно, функция next(), так как возвращается следующий класс согласно цепочке разрешения методов (MRO — Method Resolution Order).

Пример:


class Top:
def foo(self):
return 'top'

class Left(Top):
def foo(self):
return super().foo()

class Right(Top):
def foo(self):
return 'right'

class Bottom(Left, Right):
pass

# выводит 'right'
print(Bottom().foo())


Обрати внимание: результат работы super() может отличаться в зависимости от MRO вызвавшего объекта.


>>> Bottom().foo()
'right'
>>> Left().foo()
'top'


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32
Функция map вызывает другую функцию для каждого элемента итерируемого объекта. Это значит, что функция должна принимать одно значение в качестве аргумента:


In : list(map(lambda x: x ** 2, [1, 2, 3]))
Out: [1, 4, 9]


Однако если каждый элемент итерируемого объекта — это кортеж, было бы удобно передавать каждый элемент кортежа как отдельный аргумент. В Python 2 это было возможно благодаря распаковке параметров кортежа (обратите внимание на скобки):


>>> map(lambda (a, b): a + b, [(1, 2), (3, 4)])
[3, 7]


В Python 3 эта возможность исчезла, но есть другое решение — itertools.starmap. Она распаковывает кортежи за вас, будто функция вызывается со звёздочкой: f(*arg) (отсюда и название функции):


from itertools import starmap

In [3]: list(starmap(lambda a, b: a + b, [(1, 2), (3, 4)]))
Out[3]: [3, 7]


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Когда вы используете модуль multiprocessing, и в одном из процессов происходит исключение, оно передаётся в основную программу с помощью механизма сериализации (pickling). Исключение сериализуется, передаётся в другой процесс и там десериализуется обратно.

Однако сериализация исключений может быть непростой задачей. Исключение создаётся с любым количеством аргументов, которые сохраняются в атрибуте args. Эти же аргументы используются при десериализации для воссоздания объекта исключения.

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


import pickle

class TooMuchWeightError(Exception):
def __init__(self, weight):
super().__init__()
self._weight = weight

pickled = pickle.dumps(TooMuchWeightError(42))
pickle.loads(pickled)


Вызов TooMuchWeightError.__init__ приводит к вызову Exception.__init__, который устанавливает args как пустой кортеж. Этот пустой кортеж затем используется в качестве аргументов при десериализации, что, очевидно, приводит к ошибке:


TypeError: __init__() missing 1 required positional argument: 'weight'


Обходное решение — либо вообще не вызывать super().__init__() (что обычно считается плохой практикой при наследовании), либо передавать все аргументы явно в конструктор родительского класса:


class TooMuchWeightError(Exception):
def __init__(self, weight):
super().__init__(weight)
self._weight = weight


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍2
Python предоставляет мощную библиотеку для работы с датой и временем — datetime. Интересный момент: объекты datetime имеют специальный интерфейс для поддержки часовых поясов (атрибут tzinfo), однако сама библиотека `datetime реализует его лишь частично, оставляя остальную работу сторонним модулям.

Самым популярным модулем для этой задачи является pytz. Хитрость в том, что pytz не полностью соответствует интерфейсу tzinfo. В документации pytz прямо указано с самого начала: «Эта библиотека отличается от задокументированного API Python для реализаций tzinfo».

Вы не можете просто передать объект временной зоны pytz в атрибут tzinfo. Если попробуете, результат может быть абсолютно безумным:


In : paris = pytz.timezone('Europe/Paris')
In : str(datetime(2017, 1, 1, tzinfo=paris))
Out: '2017-01-01 00:00:00+00:09'


Посмотрите на этот смещение +00:09. Правильное использование pytz выглядит так:


In : str(paris.localize(datetime(2017, 1, 1)))
Out: '2017-01-01 00:00:00+01:00'


Кроме того, после любых операций с датой и временем, нужно нормализовать объект datetime, если есть вероятность смены смещения (например, на границе перехода на летнее время):


In : new_time = time + timedelta(days=2)
In : str(new_time)
Out: '2018-03-27 00:00:00+01:00'
In : str(paris.normalize(new_time))
Out: '2018-03-27 01:00:00+02:00'


Начиная с Python 3.6, рекомендуется использовать dateutil.tz вместо pytz. Он полностью совместим с tzinfo, может использоваться напрямую, не требует normalize, хотя и работает немного медленнее.

Если вам интересно, почему pytz не поддерживает API datetime, или вы хотите увидеть больше примеров, обязательно почитайте хорошую статью на эту тему.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🚀 Подборка полезных IT каналов в Max


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

https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.

1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С

Программирование C++📌
https://max.ru/cpp_lib Библиотека C/C++ разработчика

Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика

Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика

GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub

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

Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков

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

Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻

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

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

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

Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике

Вакансии 📌
https://max.ru/progjob Вакансии в IT

Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных


Бонус 📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
yield from — элегантная передача управления

Если вы пишете генераторы, которые вызывают другие генераторы — забудьте про for x in sub(): yield x. Есть способ проще и мощнее.

Оператор yield from позволяет передавать элементы из подгенератора напрямую, без лишнего кода. Но фишка не только в лаконичности — он также автоматически пробрасывает исключения и возвращаемые значения из подгенератора.

Вот классика:


def gen():
for x in range(3):
yield x

def wrapper():
for x in gen():
yield x


Можно короче и лучше:


def wrapper():
yield from gen()


Но главное — yield from пробрасывает return-значение из подгенератора (начиная с Python 3.3):


def sub():
yield 1
yield 2
return 'done'

def main():
result = yield from sub()
print('Sub returned:', result)

for _ in main():
pass
# Выведет: Sub returned: done


А ещё через yield from можно проксировать значения внутрь генератора — например, в сопрограммах:


def delegator():
result = yield from coroutine()
print('coroutine done:', result)

def coroutine():
x = yield
y = yield
return x + y

g = delegator()
next(g) # Старт
next(g) # coroutine ждет x
g.send(10) # x = 10
print(g.send(20)) # y = 20 → return 30
# Выведет: coroutine done: 30


Итог: если вы пишете генераторы — освоение yield from даст вам лаконичный синтаксис, проброс return-значений, исключений и взаимодействие на новом уровне.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
default_factory в dataclass: мощнее, чем кажется

Многие используют dataclass как удобный способ задать структуру с полями. Но редко кто по-настоящему раскрывает силу default_factory. А зря — он спасает от багов и даёт гибкость.

Когда нужно задать значение по умолчанию для поля в dataclass, логично тянуться к default=. Но если это изменяемый тип (например, список или словарь) — вас поджидает ловушка.


from dataclasses import dataclass, field

@dataclass
class User:
name: str
tags: list[str] = [] # ⚠️ опасно!


Все экземпляры User будут делить один и тот же список. То есть:


a = User("Alice")
b = User("Bob")
a.tags.append("admin")

print(b.tags) # ['admin'] 😱


Вместо этого используйте default_factory:


@dataclass
class User:
name: str
tags: list[str] = field(default_factory=list)


Теперь у каждого User будет свой список:


a = User("Alice")
b = User("Bob")
a.tags.append("admin")

print(b.tags) # []


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


import uuid

@dataclass
class Session:
id: str = field(default_factory=lambda: str(uuid.uuid4()))


Или, например, значения из окружения:


import os

@dataclass
class Config:
debug: bool = field(default_factory=lambda: os.getenv("DEBUG") == "1")


Кстати, это ещё и отличное место для внедрения DI:


@dataclass
class Service:
client: "Client" = field(default_factory=create_default_client)


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

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
🫡41👍1
Стандартный модуль json имеет интерфейс командной строки, который может быть полезен для форматирования JSON исключительно средствами Python. Модуль называется json.tool и вызывается следующим образом:


$ echo '{"a": [], "b": "c"}' | python -m json.tool
{
"a": [],
"b": "c"
}


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
Скрытые фичи Enum: как выжать максимум

Многие используют Enum как простой список констант. Но у enum.Enum в Python есть куда больше возможностей — и они могут сделать код чище и мощнее.


Вот несколько приёмов, которые мало кто использует — но зря.


1. Добавление поведения в Enum


from enum import Enum

class Status(Enum):
DRAFT = 'draft'
PUBLISHED = 'published'
ARCHIVED = 'archived'

def is_visible(self):
return self in {Status.DRAFT, Status.PUBLISHED}


Теперь Status.DRAFT.is_visible() — это просто и элегантно.


2. Enum с полями


from enum import Enum

class Color(Enum):
RED = ('#FF0000', 'danger')
GREEN = ('#00FF00', 'safe')

def __init__(self, hex_code, label):
self.hex_code = hex_code
self.label = label



Color.RED.hex_code # '#FF0000'
Color.RED.label # 'danger'



3. Автоматические значения с auto()


from enum import Enum, auto

class Role(Enum):
ADMIN = auto()
USER = auto()
GUEST = auto()


Удобно, если не важны конкретные значения, а нужны уникальные.


4. Строгая сериализация

В реальных приложениях (API, базы) лучше контролировать сериализацию enum'ов:


import json

class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Enum):
return obj.value
return super().default(obj)

json.dumps(Status.PUBLISHED, cls=CustomEncoder) # "published"



5. Сравнение по значению


Status('draft') == Status.DRAFT # True
Status('draft') is Status.DRAFT # True (enum гарантирует singleton)


Итого: Enum — это не просто константы. Это лёгкий способ инкапсулировать поведение и данные, улучшить читаемость и сделать код устойчивее к ошибкам.

📲 Мы в MAX

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


In : lst = [1, 2, 3]
In : lst.pop()
Out: 3
In : lst
Out: [1, 2]
In : lst[:0] = [4] # push
In : lst
Out: [4, 1, 2]


Однако list выглядит не очень удобно (взгляните на этот "push") и работает неэффективно.


In : lst = [0] * 10_000_000

In : %timeit lst[:0] = [1]
9.5 ms ± 111 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In : %timeit lst.pop()
84.3 ns ± 4.01 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)


Как видно, операция pop() в 100 раз быстрее, чем вставка в начало списка. Это связано с тем, как устроен list в Python: легко добавлять и удалять элементы с конца, но удаление/добавление в начало требует создания нового списка.

Для очередей лучше использовать collections.deque. Он специально для этого создан:


In : from collections import deque
In : d = deque([1] * 100_000_000)
In : %timeit d.popleft()
65 ns ± 0.436 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1
Некоторый код может выводить интересующие вас данные в 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.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Чтобы отсортировать словарь по его значениям, используйте функцию sorted с пользовательской функцией ключа:


>>> d = dict(a=1, c=3, b=2)
>>> sorted(d.items(), key=lambda item: item[1])
[('a', 1), ('b', 2), ('c', 3)]


Однако такая функция уже существует в модуле operator:


>>> from operator import itemgetter
>>> sorted(d.items(), key=itemgetter(1))
[('a', 1), ('b', 2), ('c', 3)]


Вы также можете сортировать только ключи вместо пар ключ-значение:


>>> sorted(d, key=lambda k: d[k])
['a', 'b', 'c']


И снова, эту лямбду можно заменить уже существующим методом:


>>> sorted(d, key=d.get)
['a', 'b', 'c']


📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1😁1
Популярный способ объявить абстрактный метод в Python — это выбросить исключение NotImplementedError:


def human_name(self):
raise NotImplementedError


Хотя этот подход довольно распространён и даже поддерживается IDE (например, PyCharm считает такие методы абстрактными), у него есть недостаток: ошибка возникает только при вызове метода, а не при создании экземпляра класса.

Чтобы избежать этой проблемы, используйте модуль abc:


from abc import ABCMeta, abstractmethod

class Service(metaclass=ABCMeta):
@abstractmethod
def human_name(self):
pass


Также важно помнить, что NotImplemented — это не то же самое, что NotImplementedError. NotImplemented — это специальное значение (как True и False), а не исключение. Оно используется, например, в специальных методах (__eq__(), __add__() и др.), чтобы сообщить Python, что операция не реализована для данного типа, и попытаться вызвать альтернативный метод (например, если a.__add__(b) возвращает NotImplemented, Python попробует вызвать b.__radd__(a)).

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Как упростить работу с аргументами в командной строке с помощью 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 — синтаксис и подход очень похожи.

📲 Мы в MAX

👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🚀 Подборка полезных IT каналов в Max


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

https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.

1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С

Программирование C++📌

https://max.ru/cpp_lib Библиотека C/C++ разработчика

Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика

Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика

GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub

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

Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков

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

Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻

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

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

Книги, статьи для дизайнеров 📌

https://max.ru/odesigners Статьи, книги для дизайнеров

Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике

Вакансии 📌
https://max.ru/progjob Вакансии в IT

Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных


Бонус 📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
2👎1
Создание объекта в 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


📲 Мы в MAX

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