Выкроив время, написал небольшой вывод чему же в таком случае равно исходное выражение – (55 == True) and (True is True) или же (55 == True) is True (или ещё чему-то – увидим далее) Под chain comparison будем понимать выражение вида a OP1 b OP2 c (OP1 / OP2 - доступные в Python операторы сравнения <, <=, >, >=, ==, is)
На этом можно было бы и закончить, т.к. ответ на вопрос "а как Python считает это выражение", уже дан, но рассмотрим байт-код подробнее Поставляя разные значения вместе a, b и с в выражении a OP1 b OP2 c, а также OP1 и OP2, заметим что меняются только сами значения, которые грузятся на верхушку стека (LOAD_CONST), и опкоды операторов с их аргументами (IS_OP, COMPARE_OP и т.д.) Просимулируем работы вышеприведённого байт-кода Python на самом же Python:
аргументы на стеке идут в том же порядке, в котором они передаются в вызываемую функцию, поэтому достанем их заранее
Заметим что у нас есть ранний выход в случае когда часть выражения a OP1 b равна False Затем отметим что путём SWAP и COPY у нас с самого начала на дне стека оказывается значение переменной "посередине" этого самого chain comparison – т.е. значение b Теперь у нас на верхушку стека складывается значение c, причём перед этим в стеке остаётся лишь одно значение - b, поэтому вызывается OP2 между b и c
И таким образом, зная что в Python вычисление булевых выражений происходит лениво, то однозначным аналогом выражения a OP1 b OP2 c будет (a OP1 b) and (b OP2 c)
P.S. Для проверки использовался Python 3.13.5 (main, Jun 23 2025, 16:56:36) [Clang 16.0.0 (clang-1600.0.26.4)] on darwin
Вы же предлагаете на экзамене, угадав правильный ответ с первого раза, на вопрос экзаменатора "покажите решение" ответить "а вы проверьте, ответ подходит"
Хочется также отметить неплохой планировщик / distributed task queue taskiq - достаточно прост в использовании, имеет некоторый набор батареек и различных источников для хранения расписаний, результатов выполнения и брокеров для запуска задач
Во втором листинге, где создаётся объект fsm и указаны возможные переходы, ошибка Описанию
Если текущее состояние — q0, а текущий символ — 0 или 1, выполнить переход в состояние q1. Если текущее состояние — q1, а текущий символ — 0 или 1, выполнить переход в то же состояние.
Отмечу, в данном случае squared будет не list и не tuple, а генератор Соответственно, имеем все прелести генераторов в этом случае – например, после вызова принта в squared больше не будет доступных элементов (next(squared) выбросит StopIteration) и т.д. Да и сам вывод будет просто 4 16
Для всех ОС сервер распространяется только в версии TS3, хотя клиенты доступны для TS3 и TS5. Но последняя версия, видимо, работает только через центральные серверы, но не на самохостинге.
Захостил TS через день после новостях о блокировке Discord, к self-hosted подключается как 3, так и 5 версия (у них даже где-то написано что они совместимы)
Не скажу за автора комментария выше, но меня реддит не пускает при использовании VLESS. Да, скорее всего дело в не самой лучшей / правильной настройке, я не особо заморачивался (буквально поменял домен на wikipedia.org для подмены), а возможно потому что у меня сервер на DO, но факт – при включенном VPN меня не пускает с "You’ve been blocked by network security" При этом в мобильном приложении таких проблем не наблюдаю
Да, конечно, в реальном коде в общих методах лучше созданную сущность именовать общим именем. Я просто хотел сохранить связь с указанным автором комментария методом, но, пожалуй, приму на будущее что лучше написать код правильнее, ибо связь тогда будет точно ясна
Подскажите, а планируется ли статья по миксинам и hybrid_property / hybrid_method? Первое пригождается когда появляются одинаковые поля (те же id, created_at, deleted и т.д.), а второе позволяет динамически вычислять некие параметры, связанные с объектом БД, без повторений (например посчитать кол-во children при one-to-many и прочее). Если про миксины ещё встречал статьи на хабре, то про hybrid_property как-то не сталкивался, а тема интересная и ИМХО довольно полезная
Добрый день! Да, вы абсолютно правы, я совершенно упустил момент что я у себя использую Dependency Injection, а в данной статье он не рассматривается (я не стал о нём писать, т.к. автор мог либо написать о нём в следующих статьях либо просто его не использовать, все фломастеры разные) Конечно же в коде, который я написал, у классаBaseDAO и его наследников не будет поля класса (в typing.ClassVar понимании) model, а будет поле экземпляра класса Судя по тому, что класс UserManager наследуется от Singleton, а session пробрасывается извне, то предположу что у вас user_manager создаётся где-то отдельно и используется заместо того самого DI. В таком случае я бы просто убрал декоратор, сделав методом экземпляра класса, а в методе инициализации сделал user_manager = UserManager(model=User, ...) либо просто переопределил __init__, вызвав super().__init__(model=User) Склеивая, верный вариант моего кода выше (и наиболее близкий к реальному) выглядит как-то так:
import asyncio
import dataclasses
import typing
import pydantic
import sqlalchemy as sa
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
# DTO для валидации входных данных
class UserCreateSchema(pydantic.BaseModel):
first_name: str
last_name: str
METADATA: typing.Final = sa.MetaData()
class Base(DeclarativeBase):
metadata = METADATA
class User(Base):
__tablename__ = "user"
id: Mapped[typing.Annotated[int, mapped_column(sa.BigInteger, primary_key=True)]]
first_name: Mapped[typing.Annotated[str, mapped_column(sa.String(length=64))]]
last_name: Mapped[typing.Annotated[str, mapped_column(sa.String(length=64))]]
@dataclasses.dataclass(kw_only=True)
class BaseORMRepository[T: Base, V: pydantic.BaseModel]: # T - класс модели-объекта, V - класс модели-валидатора
model: type[T]
async def create(self, session: AsyncSession, validated_value: V) -> T:
user = self.model(**validated_value.model_dump())
session.add(user)
# await session.commit() # закомментировано чтобы код мог запуститься без реального подключения к базе
return user
class UserManager(BaseORMRepository[User, UserCreateSchema]):
def __init__(self) -> None:
super().__init__(model=User)
if __name__ == "__main__":
user_manager = UserManager()
session = AsyncSession(bind=None) # представим что это настоящая сессия
user_validated = UserCreateSchema(first_name="John", last_name="Doe")
user_created = asyncio.run(user_manager.create(session, user_validated))
print(user_created.first_name, user_created.last_name)
Что выведет John Doe в терминал. Да, теряется краткость за счёт переопределения __init__, но приобретается другой момент – все CRUD-методы внутри BaseORMRepository можно правильно типизировать, уменьшив вероятность ошибок
Заглянем в книгу Правила русской орфографии и пунктуации. Полный академический справочник под редакцией В.В. Лопатина:
§ 115. Знак апостроф — надстрочная запятая — имеет в русском письме ограниченное применение (...) Апострофом отделяются русские окончания и суффиксы от предшествующей части слова, передаваемой латинскими буквами, напр.: Он инструментовал сочиненную летом c-moll’ную увертюру (Берб.)
Статья вроде бы неплоха, но есть нюансы, которые в какой-то момент оказываются важны, но не рассмотрены, или просто использованы bad practice
Декоратор def connection(method): – очень не хватает уровня изоляции для транзакции со значением по-умолчанию. Да, в большинстве случаев будет стандартный read commiеted, но когда нужно выбрать другой для всего UnitOfWork – хочется передать его в декоратор, а не писать в коде уродливое await session.execute(text("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ")), ещё и часто на уровне сервисного слоя, а не уровне репозитория. Ведь всё равно session будет закрыта по завершении вызова декорированного метода
class BaseDAO:
model = None # Устанавливается в дочернем классе
Вот этот момент прям боль. И я даже не про отсутствие типа у поля model. У нас же есть дженерики. Почему не использовать их? Python < 3.12:
import typing
T = typing.TypeVar("T", bound=Base) # мы можем задать границу типа, т.о. мы будем уверены при статическом анализе что использованы верные типы как минимум в иерархии
class BaseDAO(typing.Generic[T]):
model: type[T]
Python >= 3.12:
# точно так же можно задать границу дженерика
class BaseDAO[T: Base]:
model: type[T]
class UserDAO(BaseDAO[User]):
...
class ProfileDAO(BaseDAO[Profile]):
...
Чем же лучше? А тем, что мы не можем не указать класс – статический анализ отвалится. А вот забыть написать model = XXX вполне можно, и mypy даже не ругнётся при model: Base
async def find_all(cls, session: AsyncSession, **filter_by), async def find_one_or_none(cls, session: AsyncSession, **filter_by): и прочие, где передаются **kwargs – я бы сказал а-та-та, очень просто словить из-за опечатки ошибку в запросе из-за того, что передаётся ключ, ссылающийся на несуществующую колонку. Лучше сделать pydantic-модель со всеми нужными опциональными полями, передавать её и делать query = select(сls.model).filter_by(**filters.model_dump(exclude_unset=True))
Сам Гвидо когда-то писал что лучше использовать list comprehension вместо map, т.к. производительность выше (источник – http://python-history.blogspot.com/2010/06/from-list-comprehensions-to-generator.html). Да и в принципе сейчас легко гуглится что list comprehension заменяет и map, и filter в одном Да, map / filter возвращают не массив, а генератор, но в контексте, который представлен в статье, берётся list от этого генератора, и смысл пропадает. При этом есть возможность таким же образом создать и генератор (Py 3.11):
a = [1, 2, 3, 4]
a_map_list = [x*x for x in a] # вернёт [1, 4, 9, 16]
a_map_gen = (x*x for x in a) # вернёт generator, который при итерации выдаст те же значения, что и при итерации по `a_map_list`
Напрямую...куда? На 127.0.0.1/<эндпоинт>? Чем это будет отличаться от
location / {
proxy_pass http://127.0.0.1;
}
?
Ну и на более глубоких материях - масштабирование по горизонтали)
А разве горизонтальное масштабирование как раз не достигается с помощью того же round-robing балансировки? Прямой же доступ как раз усложнит такое. С одной стороны через nginx прописать RR и он сам будет крутить адреса в локальной сети, с другой - на клиенте менять порт приложения, на который нужно сходить. Что-то у вас не сходится
Да, но это легко решается с помощью glAlphaFunc, glBlendEquation с параметрами GL_MIN либо GL_MAX, или же GL_DEPTH_TEST, что, конечно, намного проще, чем описанное в статье
Мне кажется что в этом случае ContextVar выступает в роле ThreadLocal, только для питона AsyncContextLocal-переменной. Так как автор явно упомянул что пользуется этим кодом для Telegram-ботов и веба, а также учитывая что функции async, то "обернуть" сессию каждого запроса в ContextVar для меня кажется разумным решением чтобы сохранить их независимость друг от друга
Выкроив время, написал небольшой вывод чему же в таком случае равно исходное выражение –
(55 == True) and (True is True)
или же(55 == True) is True
(или ещё чему-то – увидим далее)Под chain comparison будем понимать выражение вида
a OP1 b OP2 c
(OP1
/OP2
- доступные в Python операторы сравнения<
,<=
,>
,>=
,==
,is
)На этом можно было бы и закончить, т.к. ответ на вопрос "а как Python считает это выражение", уже дан, но рассмотрим байт-код подробнее
Поставляя разные значения вместе
a
,b
ис
в выраженииa OP1 b OP2 c
, а такжеOP1
иOP2
, заметим что меняются только сами значения, которые грузятся на верхушку стека (LOAD_CONST
), и опкоды операторов с их аргументами (IS_OP
,COMPARE_OP
и т.д.)Просимулируем работы вышеприведённого байт-кода Python на самом же Python:
Небольшая ремарка про
аргументы на стеке идут в том же порядке, в котором они передаются в вызываемую функцию, поэтому достанем их заранее
Заметим что у нас есть ранний выход в случае когда часть выражения
a OP1 b
равнаFalse
Затем отметим что путём
SWAP
иCOPY
у нас с самого начала на дне стека оказывается значение переменной "посередине" этого самого chain comparison – т.е. значениеb
Теперь у нас на верхушку стека складывается значение
c
, причём перед этим в стеке остаётся лишь одно значение -b
, поэтому вызываетсяOP2
междуb
иc
И таким образом, зная что в Python вычисление булевых выражений происходит лениво, то однозначным аналогом выражения
a OP1 b OP2 c
будет(a OP1 b) and (b OP2 c)
P.S. Для проверки использовался
Python 3.13.5 (main, Jun 23 2025, 16:56:36) [Clang 16.0.0 (clang-1600.0.26.4)] on darwin
Единственный правильный способ узнать как Python интерпретирует это выражение – это
Вы же предлагаете на экзамене, угадав правильный ответ с первого раза, на вопрос экзаменатора "покажите решение" ответить "а вы проверьте, ответ подходит"
Хочется также отметить неплохой планировщик / distributed task queue taskiq - достаточно прост в использовании, имеет некоторый набор батареек и различных источников для хранения расписаний, результатов выполнения и брокеров для запуска задач
Во втором листинге, где создаётся объект fsm и указаны возможные переходы, ошибка
Описанию
Соответствует следующая таблица переходов:
Т.к. в оригинальном листинге даже при следующем символе из алфавита при стартовом состоянии
q0
мы из состоянияq0
мы можем перейти только вq0
Отмечу, в данном случае
squared
будет неlist
и неtuple
, а генераторСоответственно, имеем все прелести генераторов в этом случае – например, после вызова принта в
squared
больше не будет доступных элементов (next(squared)
выброситStopIteration
) и т.д.Да и сам вывод будет просто
4 16
Захостил TS через день после новостях о блокировке Discord, к self-hosted подключается как 3, так и 5 версия (у них даже где-то написано что они совместимы)
Не скажу за автора комментария выше, но меня реддит не пускает при использовании VLESS. Да, скорее всего дело в не самой лучшей / правильной настройке, я не особо заморачивался (буквально поменял домен на wikipedia.org для подмены), а возможно потому что у меня сервер на DO, но факт – при включенном VPN меня не пускает с "You’ve been blocked by network security"
При этом в мобильном приложении таких проблем не наблюдаю
Да, конечно, в реальном коде в общих методах лучше созданную сущность именовать общим именем. Я просто хотел сохранить связь с указанным автором комментария методом, но, пожалуй, приму на будущее что лучше написать код правильнее, ибо связь тогда будет точно ясна
Подскажите, а планируется ли статья по миксинам и
hybrid_property
/hybrid_method
? Первое пригождается когда появляются одинаковые поля (те жеid
,created_at
,deleted
и т.д.), а второе позволяет динамически вычислять некие параметры, связанные с объектом БД, без повторений (например посчитать кол-во children при one-to-many и прочее). Если про миксины ещё встречал статьи на хабре, то проhybrid_property
как-то не сталкивался, а тема интересная и ИМХО довольно полезнаяДобрый день!
Да, вы абсолютно правы, я совершенно упустил момент что я у себя использую Dependency Injection, а в данной статье он не рассматривается (я не стал о нём писать, т.к. автор мог либо написать о нём в следующих статьях либо просто его не использовать, все фломастеры разные)
Конечно же в коде, который я написал, у класса
BaseDAO
и его наследников не будет поля класса (вtyping.ClassVar
понимании)model
, а будет поле экземпляра классаСудя по тому, что класс
UserManager
наследуется отSingleton
, аsession
пробрасывается извне, то предположу что у васuser_manager
создаётся где-то отдельно и используется заместо того самого DI. В таком случае я бы просто убрал декоратор, сделав методом экземпляра класса, а в методе инициализации сделалuser_manager = UserManager(model=User, ...)
либо просто переопределил__init__
, вызвавsuper().__init__(model=User)
Склеивая, верный вариант моего кода выше (и наиболее близкий к реальному) выглядит как-то так:
Что выведет
John Doe
в терминал. Да, теряется краткость за счёт переопределения__init__
, но приобретается другой момент – все CRUD-методы внутриBaseORMRepository
можно правильно типизировать, уменьшив вероятность ошибокЗаглянем в книгу Правила русской орфографии и пунктуации. Полный академический справочник под редакцией В.В. Лопатина:
Статья вроде бы неплоха, но есть нюансы, которые в какой-то момент оказываются важны, но не рассмотрены, или просто использованы bad practice
Декоратор
def connection(method):
– очень не хватает уровня изоляции для транзакции со значением по-умолчанию. Да, в большинстве случаев будет стандартныйread commiеted
, но когда нужно выбрать другой для всего UnitOfWork – хочется передать его в декоратор, а не писать в коде уродливоеawait session.execute(text("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ"))
, ещё и часто на уровне сервисного слоя, а не уровне репозитория. Ведь всё равноsession
будет закрыта по завершении вызова декорированного методаВот этот момент прям боль. И я даже не про отсутствие типа у поля
model
. У нас же есть дженерики. Почему не использовать их?Python < 3.12:
Python >= 3.12:
И используем наш
T
для типизации где хотим:И сами dao-классы описываются лучше:
Чем же лучше? А тем, что мы не можем не указать класс – статический анализ отвалится. А вот забыть написать
model = XXX
вполне можно, и mypy даже не ругнётся приmodel: Base
async def find_all(cls, session: AsyncSession, **filter_by)
,async def find_one_or_none(cls, session: AsyncSession, **filter_by):
и прочие, где передаются**kwargs
– я бы сказал а-та-та, очень просто словить из-за опечатки ошибку в запросе из-за того, что передаётся ключ, ссылающийся на несуществующую колонку. Лучше сделать pydantic-модель со всеми нужными опциональными полями, передавать её и делатьquery = select(сls.model).filter_by(**filters.model_dump(exclude_unset=True))
Сам Гвидо когда-то писал что лучше использовать list comprehension вместо map, т.к. производительность выше (источник – http://python-history.blogspot.com/2010/06/from-list-comprehensions-to-generator.html). Да и в принципе сейчас легко гуглится что list comprehension заменяет и map, и filter в одном
Да, map / filter возвращают не массив, а генератор, но в контексте, который представлен в статье, берётся list от этого генератора, и смысл пропадает. При этом есть возможность таким же образом создать и генератор (Py 3.11):
Из-за особенностей векторизации f32-вычислений (отсутствие ассоциативности)
.map(..).sum()
можно безопасно заменить на.fold
:Вот тут можно увидеть что варианты для i32 используют SIMD, а для f32 - уже нет
https://rust.godbolt.org/z/9T464GToG
Напрямую...куда? На 127.0.0.1/<эндпоинт>? Чем это будет отличаться от
?
А разве горизонтальное масштабирование как раз не достигается с помощью того же round-robing балансировки? Прямой же доступ как раз усложнит такое. С одной стороны через nginx прописать RR и он сам будет крутить адреса в локальной сети, с другой - на клиенте менять порт приложения, на который нужно сходить. Что-то у вас не сходится
Заголовочные файлы и define для запуска примера:
Да, но это легко решается с помощью
glAlphaFunc
,glBlendEquation
с параметрамиGL_MIN
либоGL_MAX
, или жеGL_DEPTH_TEST
, что, конечно, намного проще, чем описанное в статьеМне кажется что в этом случае
ContextVar
выступает в ролеThreadLocal
, только для питонаAsyncContextLocal
-переменной. Так как автор явно упомянул что пользуется этим кодом для Telegram-ботов и веба, а также учитывая что функцииasync
, то "обернуть" сессию каждого запроса вContextVar
для меня кажется разумным решением чтобы сохранить их независимость друг от другаВо втором примере описка
До вывода типов:
Сам вывод типов:
После подстановки тип
?2
равенint
вместоList<int>
Подозреваю что на самом деле там должно быть
cache_value