All streams
Search
Write a publication
Pull to refresh
2
0
Георгий Имешкенов @Veritaris

User

Send message

Выкроив время, написал небольшой вывод чему же в таком случае равно исходное выражение – (55 == True) and (True is True) или же (55 == True) is True (или ещё чему-то – увидим далее)
Под chain comparison будем понимать выражение вида a OP1 b OP2 c (OP1 / OP2 - доступные в Python операторы сравнения <, <=, >, >=, ==, is)

>>> import dis
>>> dis.dis("55 == True is True")
  0           RESUME                   0
  1           LOAD_CONST               0 (55)
              LOAD_CONST               1 (True)
              SWAP                     2
              COPY                     2
              COMPARE_OP              72 (==)
              COPY                     1
              TO_BOOL
              POP_JUMP_IF_FALSE        4 (to L1)
              POP_TOP
              LOAD_CONST               1 (True)
              IS_OP                    0
              RETURN_VALUE
      L1:     SWAP                     2
              POP_TOP
              RETURN_VALUE

На этом можно было бы и закончить, т.к. ответ на вопрос "а как Python считает это выражение", уже дан, но рассмотрим байт-код подробнее
Поставляя разные значения вместе a, b и с в выражении a OP1 b OP2 c, а также OP1 и OP2, заметим что меняются только сами значения, которые грузятся на верхушку стека (LOAD_CONST), и опкоды операторов с их аргументами (IS_OP, COMPARE_OP и т.д.)
Просимулируем работы вышеприведённого байт-кода Python на самом же Python:

def vm_simulate() -> bool:
    stack = []
    stack.append(55)                                # LOAD_CONST               0 (55)
    stack.append(True)                              # LOAD_CONST               1 (True)
    stack[-2], stack[-1] = stack[-1], stack[-2]     # SWAP                     2
    stack.append(stack[-2])                         # COPY                     2
    op_right = stack.pop()
    op_left = stack.pop()
    stack.append(op_left == op_right)               # COMPARE_OP              72 (==)
    stack.append(stack[-1])                         # COPY
    stack[-1] = bool(stack[-1])                     # TO_BOOL

    if not (stack.pop()):                           # POP_JUMP_IF_FALSE        4 (to L1)
        stack[-2], stack[-1] = stack[-1], stack[-2] # SWAP                     2
        stack.pop()                                 # POP_TOP
        return stack[-1]                            # RETURN_VALUE

    stack.pop()                                     # POP_TOP
    stack.append(True)                              # LOAD_CONST               1 (True)
    op_right = stack.pop()
    op_left = stack.pop()
    stack.append(op_left is op_right)              # IS_OP                    0

    return stack[-1]                               # RETURN_VALUE

Небольшая ремарка про

op_right = stack.pop()
op_left = stack.pop()

аргументы на стеке идут в том же порядке, в котором они передаются в вызываемую функцию, поэтому достанем их заранее

Заметим что у нас есть ранний выход в случае когда часть выражения 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 интерпретирует это выражение – это

import dis
dis.dis("55 == True is True")
  0           RESUME                   0
  1           LOAD_CONST               0 (55)
              LOAD_CONST               1 (True)
              SWAP                     2
              COPY                     2
              COMPARE_OP              72 (==)
              COPY                     1
              TO_BOOL
              POP_JUMP_IF_FALSE        4 (to L1)
              POP_TOP
              LOAD_CONST               1 (True)
              IS_OP                    0
              RETURN_VALUE
      L1:     SWAP                     2
              POP_TOP
              RETURN_VALUE

Вы же предлагаете на экзамене, угадав правильный ответ с первого раза, на вопрос экзаменатора "покажите решение" ответить "а вы проверьте, ответ подходит"

Хочется также отметить неплохой планировщик / distributed task queue taskiq - достаточно прост в использовании, имеет некоторый набор батареек и различных источников для хранения расписаний, результатов выполнения и брокеров для запуска задач

Во втором листинге, где создаётся объект fsm и указаны возможные переходы, ошибка
Описанию

Если текущее состояние — q0, а текущий символ — 0 или 1, выполнить переход в состояние q1.
Если текущее состояние — q1, а текущий символ — 0 или 1, выполнить переход в то же состояние.

Соответствует следующая таблица переходов:

  q0: {
    0: 'q1',
    1: 'q1'
  },
  q1: {
    0: 'q1',
    1: 'q1'
  }

Т.к. в оригинальном листинге даже при следующем символе из алфавита при стартовом состоянии q0 мы из состояния q0 мы можем перейти только в q0

Отмечу, в данном случае 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

  1. Декоратор 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]

И используем наш T для типизации где хотим:

class BaseDAO[T: Base]:
    model: type[T]

    @classmethod
    async def add(cls, session: AsyncSession, **values: dict[str, typing.Any]) -> T:
        new_instance = cls.model(**values)
        session.add(new_instance)
        try:
            await session.commit()
        except SQLAlchemyError:
            await session.rollback()
            raise
        return new_instance

    @classmethod
    async def add_many(cls, session: AsyncSession, instances: list[dict[str, typing.Any]]) -> list[T]:
        new_instances = [cls.model(**values) for values in instances]
        session.add_all(new_instances)
        try:
            await session.commit()
        except SQLAlchemyError:
            await session.rollback()
            raise
        return new_instances

И сами dao-классы описываются лучше:

class UserDAO(BaseDAO[User]):
    ...


class ProfileDAO(BaseDAO[Profile]):
    ...

Чем же лучше? А тем, что мы не можем не указать класс – статический анализ отвалится. А вот забыть написать model = XXX вполне можно, и mypy даже не ругнётся при model: Base

  1. 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))

Использование map и filter

Сам Гвидо когда-то писал что лучше использовать 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`

Из-за особенностей векторизации f32-вычислений (отсутствие ассоциативности) .map(..).sum() можно безопасно заменить на .fold:

let mut output = zip(inputs, &self.weights)
            .into_iter()
            .map(|(input, weight)| input * weight)
            .sum();
let mut output = zip(inputs, &self.weights)
            .into_iter()
            .fold(0.0, |acc, (input, weight)| acc + input * weight);

Вот тут можно увидеть что варианты для i32 используют SIMD, а для f32 - уже нет
https://rust.godbolt.org/z/9T464GToG

Напрямую...куда? На 127.0.0.1/<эндпоинт>? Чем это будет отличаться от

location / {
    proxy_pass http://127.0.0.1;
}

?

Ну и на более глубоких материях - масштабирование по горизонтали)

А разве горизонтальное масштабирование как раз не достигается с помощью того же round-robing балансировки? Прямой же доступ как раз усложнит такое. С одной стороны через nginx прописать RR и он сам будет крутить адреса в локальной сети, с другой - на клиенте менять порт приложения, на который нужно сходить. Что-то у вас не сходится

Заголовочные файлы и define для запуска примера:

#include <stdio.h>
#include <stdlib.h>

#include <mach/mach.h>

#define EXIT_ON_MACH_ERROR(msg, retval) \
        if (kr != KERN_SUCCESS) { mach_error(msg ":" , kr); exit((retval)); }

Да, но это легко решается с помощью glAlphaFunc, glBlendEquation с параметрами GL_MIN либо GL_MAX, или же GL_DEPTH_TEST, что, конечно, намного проще, чем описанное в статье

Мне кажется что в этом случае ContextVar выступает в роле ThreadLocal, только для питона AsyncContextLocal-переменной. Так как автор явно упомянул что пользуется этим кодом для Telegram-ботов и веба, а также учитывая что функции async, то "обернуть" сессию каждого запроса в ContextVar для меня кажется разумным решением чтобы сохранить их независимость друг от друга

Во втором примере описка

До вывода типов:

func generate_nums(count: ?1) -> ?2 {
  var a: ?3 = [];
  ...
  return a;
}

Сам вывод типов:

?2 = Array<int>
?3 = Array<int>

После подстановки тип ?2 равен int вместо List<int>

func generate_nums(count: int) -> int {
  var a: List<int> = [];
  for (var i: int = 0; i < count; i++) {
    a.insert(i);
  }
  return a;
}

Подозреваю что на самом деле там должно быть cache_value

Information

Rating
Does not participate
Location
Россия
Date of birth
Registered
Activity

Specialization

Software Developer, Backend Developer
Middle
Java
Python
Docker
Git
Nginx
Bash
C