Advertisement · 728 × 90

Posts by Maxim Sakhno

Screenshot of Python code showing a type error: lst.append(1) is flagged because parameter lst is typed as Sequence[int], which has no append method.

Screenshot of Python code showing a type error: lst.append(1) is flagged because parameter lst is typed as Sequence[int], which has no append method.

Yeah, it's painful. Luckily with type annotations you can explicitly indicate what a function may mutate and what it can't.

1 month ago 2 0 0 0
A screenshot of the VS Code editor showing a Python code snippet with a static analysis error from Pyright.

Inside a function named process, the variable result is underlined with a red squiggle on line 324 in the statement return result. A tooltip overlay displays the warning: "result" is possibly unbound (reportPossiblyUnboundVariable).

A screenshot of the VS Code editor showing a Python code snippet with a static analysis error from Pyright. Inside a function named process, the variable result is underlined with a red squiggle on line 324 in the statement return result. A tooltip overlay displays the warning: "result" is possibly unbound (reportPossiblyUnboundVariable).

Python is pretty livable with linters 😄

1 month ago 1 0 1 0

I think working with statically typed languages in general makes you a better programmer

1 month ago 1 0 1 0

Looks like Anthropic wins this round — here's my $100 🫡

1 month ago 2 0 0 0

Settled on AMD Ryzen AI Max+ 395 128GB for its price/performance balance, but will wait for builds from trusted manufacturers — don't want to deal with teething issues of first-wave mini PCs on this chip. #AI #LLM

1 month ago 3 0 1 0

Really want to bombard AI with tons of questions and tasks for my projects without worrying about limits on cloud providers' paid plans. Plus, use the same setup for both work and personal projects.

1 month ago 2 0 0 0

Can't stop thinking about running LLMs locally. Tried it on my AMD Ryzen 7 8845H + Radeon 780M 32GB — not enough for serious models. Now eyeing a MacBook, NVIDIA DGX Spark, or AMD Ryzen AI Max+ 395 128GB (my favorite so far). #AI #LLM

1 month ago 2 0 2 1

Oh! Never really thought before that tox could be used for anything other than different Python versions 😀

1 month ago 1 0 0 0
Advertisement

Investigated why our lightly-loaded #FastAPI service needed 10 replicas. Optimized from 80 to 9000 RPS locally 🚀

The culprits?
- Sentry SDK caused 3.5x slowdown
- Sync dependencies (simple functions, no IO) caused 30x slowdown 😱

Still can't believe the numbers are real! #Python

2 months ago 4 0 0 0
A bar chart comparing the performance of LiteStar (orange bars) and FastAPI (green bars) in Requests Per Second (RPS) as the number of dependencies ('Num Deps') increases from 0 to 5. The data shows that LiteStar consistently maintains a higher throughput than FastAPI across all dependency counts. For example, at 0 dependencies, LiteStar reaches 45573 RPS versus FastAPI's 37004 RPS. At 5 dependencies, LiteStar holds 32877 RPS while FastAPI drops to 26463 RPS.

A bar chart comparing the performance of LiteStar (orange bars) and FastAPI (green bars) in Requests Per Second (RPS) as the number of dependencies ('Num Deps') increases from 0 to 5. The data shows that LiteStar consistently maintains a higher throughput than FastAPI across all dependency counts. For example, at 0 dependencies, LiteStar reaches 45573 RPS versus FastAPI's 37004 RPS. At 5 dependencies, LiteStar holds 32877 RPS while FastAPI drops to 26463 RPS.

Testing #FastAPI vs #LiteStar with nested empty deps (0-5 levels).

Both show smooth perf degradation, unlike LiteStar's flat deps with TaskGroup.

Still, 5 deps = 28% drop for LiteStar — DI overhead is significant even when deps do nothing.

#Python

2 months ago 4 2 0 1

The post's goal was to show TaskGroup overhead remains significant even with real work: 1 HTTP request + 4 DB queries still drops RPS by 17%. With empty dependencies, it was -56%.

BTW, FastAPI doesn't parallelize dependencies—just awaits them sequentially—and no one seems to complain about it.

2 months ago 2 0 0 0

I'm not against parallelizing slow IO. The problem is LiteStar always parallelizes dependencies despite high overhead—even when they don't do IO. In my experience, most dependencies just extract/transform request data or create instances. LiteStar parallelizes these too, adding cost with no benefit.

2 months ago 2 0 2 0

If the DB responds slower, what does the app do while waiting? It processes other requests. So with slower DB queries, we'd simply run more parallel requests—and hit the same CPU bottleneck again. TaskGroup overhead affects RPS even with slow IO operations.

2 months ago 0 0 1 0
Preview
GitHub - maximsakhno/di-benchmarks: Performance benchmark comparing Litestar vs FastAPI dependency injection overhead. Performance benchmark comparing Litestar vs FastAPI dependency injection overhead. - maximsakhno/di-benchmarks

The app sends SELECT 42 to Postgres on the same machine. The machine wasn't fully loaded—many CPU cores were idle. However, the LiteStar worker maxed out one core at 100%. The slowdown came from TaskGroup overhead, not DB/app resource contention. Benchmark: github.com/maximsakhno/...

2 months ago 0 0 1 0
A bell curve meme titled 'IQ distribution' illustrating different approaches to dependency resolution. The left side (low IQ) and right side (high IQ) show a simple character saying 'dumbly await', representing a straightforward sequential approach. The peak of the curve (average IQ) shows a crying character advocating to 'parallelize with TaskGroup', suggesting that complex parallelization is often over-engineered.

A bell curve meme titled 'IQ distribution' illustrating different approaches to dependency resolution. The left side (low IQ) and right side (high IQ) show a simple character saying 'dumbly await', representing a straightforward sequential approach. The peak of the curve (average IQ) shows a crying character advocating to 'parallelize with TaskGroup', suggesting that complex parallelization is often over-engineered.

2 months ago 2 0 0 0
Enhancement: Optimize dependency resolution overhead (TaskGroup bottleneck) · Issue #4563 · litestar-org/litestar Summary Benchmarks show a significant performance degradation in dependency resolution when increasing the number of flat dependencies. Specifically, moving from 1 to 2 dependencies results in a sh...

Conclusion: The results further confirm the hypothesis that RPS degrades specifically due to high TaskGroup overhead. 🛠️ This optimization is exactly what I’m proposing in the GitHub issue: github.com/litestar-org...

2 months ago 0 0 1 0
Advertisement
A screenshot of Python source code from LiteStar's dependency resolver with handwritten annotations. The code shows a loop iterating through "dependency_batches." An if-statement checks if a batch size is 1; if so, it simply awaits the dependency. The "else" block reveals that for any batch larger than 1, LiteStar spawns an async TaskGroup to resolve dependencies in parallel. Annotations highlight that this TaskGroup creation is the source of the performance overhead when moving beyond a single dependency.

A screenshot of Python source code from LiteStar's dependency resolver with handwritten annotations. The code shows a loop iterating through "dependency_batches." An if-statement checks if a batch size is 1; if so, it simply awaits the dependency. The "else" block reveals that for any batch larger than 1, LiteStar spawns an async TaskGroup to resolve dependencies in parallel. Annotations highlight that this TaskGroup creation is the source of the performance overhead when moving beyond a single dependency.

A technical code snippet showing a Python for loop iterating through dependency batches and dependency objects. Annotated arrows explain the logic: 'Iterating through batches in resolution order', 'Iterating through each dependency in the batch', and 'Just dumbly awaiting all dependencies', highlighting a sequential approach to resolving dependencies.

A technical code snippet showing a Python for loop iterating through dependency batches and dependency objects. Annotated arrows explain the logic: 'Iterating through batches in resolution order', 'Iterating through each dependency in the batch', and 'Just dumbly awaiting all dependencies', highlighting a sequential approach to resolving dependencies.

The setup: The original dep resolution algorithm uses a TaskGroup when multiple deps can be resolved in parallel. We're going to change it so deps are always resolved sequentially (awaited).

2 months ago 0 0 1 0
A technical diagram and code snippet comparing dependency injection structures in a Python web framework. The left side shows asynchronous code for three dependencies (dep11, dep21, dep22) and two GET handlers. 'handler1' is linked to 'dep11', which performs 4 database queries. 'handler2' is linked to two dependencies, 'dep21' and 'dep22', each performing 2 database queries. The right side features a flowchart visualizing these relationships, showing how one handler uses a single heavy dependency while the other splits the workload into two smaller ones, both totaling 4 DB queries.

A technical diagram and code snippet comparing dependency injection structures in a Python web framework. The left side shows asynchronous code for three dependencies (dep11, dep21, dep22) and two GET handlers. 'handler1' is linked to 'dep11', which performs 4 database queries. 'handler2' is linked to two dependencies, 'dep21' and 'dep22', each performing 2 database queries. The right side features a flowchart visualizing these relationships, showing how one handler uses a single heavy dependency while the other splits the workload into two smaller ones, both totaling 4 DB queries.

Methodology: I tested endpoints with 1, 2, and 4 flat dependencies. To isolate the dependency resolution logic overhead, the total workload remains constant: 4 DB queries per request, distributed evenly among the dependencies.

2 months ago 0 0 1 0
A bar chart comparing performance in Requests Per Second (RPS) between 'TaskGroup' (purple bars) and 'Await' (orange bars) based on the number of dependencies ('Num Deps'). For 1 dependency, TaskGroup achieves 4604 RPS and Await achieves 4628 RPS. For 2 dependencies, TaskGroup is at 4085 RPS while Await is at 4558 RPS. For 4 dependencies, TaskGroup drops to 3817 RPS, whereas Await maintains 4529 RPS, showing that Await scales better as dependency count increases.

A bar chart comparing performance in Requests Per Second (RPS) between 'TaskGroup' (purple bars) and 'Await' (orange bars) based on the number of dependencies ('Num Deps'). For 1 dependency, TaskGroup achieves 4604 RPS and Await achieves 4628 RPS. For 2 dependencies, TaskGroup is at 4085 RPS while Await is at 4558 RPS. For 4 dependencies, TaskGroup drops to 3817 RPS, whereas Await maintains 4529 RPS, showing that Await scales better as dependency count increases.

Quick perf test: changed #LiteStar dependency resolution from TaskGroup to await.

RPS impact? Minimal vs 17% degradation with TaskGroup at 4 deps. Each request does 4 DB calls.

TaskGroup overhead matters more than expected.

More details in thread 👇

#Python #Backend

2 months ago 4 2 2 1
Как в Python применяется инверсия зависимостей. Максим Сахно, Контур
Как в Python применяется инверсия зависимостей. Максим Сахно, Контур YouTube video by Видео с мероприятий {speach!

My #PyCon RU 2025 talk (Best Speaker award! 🏆) is now on YouTube: youtu.be/MpQgrhLO6aE

English subtitles available! 🇬🇧

I break down the Dependency Inversion Principle, compare popular #Python #DI frameworks, and show why they matter for building better applications.

2 months ago 4 2 1 1

Our ints take like 28 bytes in memory. You really think we care about memory? 😂

2 months ago 1 0 0 0

Nim has a similar idea - combines compiled language speed with Python-like simplicity. I've had a really positive experience with it, but unfortunately it's still under the radar.

2 months ago 2 0 0 0

Fun fact: sum(0.1 for _ in range(10)) == 1 also returns False for the same reason. Math has left the chat 😄

2 months ago 0 0 0 0

It's IEEE 754 floats - the decimal 10.555 can't be exactly represented in binary (64-bit), so it's actually stored as ~10.55499999999999971578. Try print(f"{10.555:.20f}") and you'll see! Not a Python thing, all languages have this.

2 months ago 0 0 0 0

It's lazy evaluated too - if the first if is False, the rest won't even be checked

2 months ago 0 0 0 0
Advertisement
A low-resolution, close-up image of the 'Surprised Pikachu' meme. The yellow Pokémon is shown with wide eyes and an open mouth, capturing a classic expression of mock shock or disbelief.

A low-resolution, close-up image of the 'Surprised Pikachu' meme. The yellow Pokémon is shown with wide eyes and an open mouth, capturing a classic expression of mock shock or disbelief.

Wait, this is actually allowed?! 🤯

2 months ago 1 0 0 0

If you interpret self.x += 1 literally: first we grab the class attribute, add 1 to it, then assign the result to the instance. So it should be A

2 months ago 0 0 0 0

In C/C++ you could manipulate this to return A (pointers, references, etc.), but Python's semantics make it clearly B

2 months ago 0 0 0 0

Same! Can't wait for t-string support to land in libraries and frameworks. More static analysis checks, less runtime surprises!

2 months ago 1 0 0 0

That said, for one-off disposable scripts? Yeah, type hints might be overkill

2 months ago 1 0 1 0