<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yuri Penskikh</title>
    <description>The latest articles on DEV Community by Yuri Penskikh (@gagatust).</description>
    <link>https://dev.to/gagatust</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3979301%2Fa4bfcd32-73b1-439d-b134-b1714c09349e.png</url>
      <title>DEV Community: Yuri Penskikh</title>
      <link>https://dev.to/gagatust</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gagatust"/>
    <language>en</language>
    <item>
      <title>The Pianola: Your Computer's Grandmother</title>
      <dc:creator>Yuri Penskikh</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:41:08 +0000</pubDate>
      <link>https://dev.to/gagatust/the-pianola-your-computers-grandmother-1b0b</link>
      <guid>https://dev.to/gagatust/the-pianola-your-computers-grandmother-1b0b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; We break down the inner workings of a 19th-century mechanical player piano using modern IT terms, and discover why a self-playing piano is actually a mechanical 88-bit processor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Remember those old Western movies? A sheriff walks into a saloon, faces around look grim, and in the corner, a piano is playing all by itself—the keys moving as if pressed by a ghost. That's a pianola (or self-playing piano). Back in the day, people genuinely wondered: what kind of "devilish machine" is this? How does it even work? How can a lifeless object perform complex musical pieces?&lt;/p&gt;

&lt;p&gt;As it turns out, inside this machine lie the exact same fundamental principles that power the CPU in your computer or phone today. And it all starts with air.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taming Air: How It Works
&lt;/h2&gt;

&lt;p&gt;Inside a pianola, you'll find the same strings and hammers as a regular piano. But instead of human fingers, it's air pressure doing the heavy lifting.&lt;/p&gt;

&lt;p&gt;Let's run a thought experiment and look at a simplified, single-note version of a pianola. We will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A piano string and a hammer.&lt;/strong&gt; Our ultimate goal is to make the hammer strike the string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An airtight rubber pouch (bellows).&lt;/strong&gt; This is mechanically linked to the hammer and moves it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two tubes connected to this pouch.&lt;/strong&gt; The first tube connects the pouch to a vacuum pump (in a real instrument, the air is pumped out using foot pedals). The second tube ends with a rigid metal tip exposed to the outside. This is what we will cover and uncover.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Logical Zero (System Silence)
&lt;/h3&gt;

&lt;p&gt;We turn on the pump and tightly block the open end of the control tube with a finger. The system is now airtight, and the pump completely sucks the air out of the rubber pouch, creating a vacuum (low pressure). At this moment, external atmospheric pressure starts pushing on the rubber from all sides. Under this pressure, our pouch collapses completely into a flat pancake. The hammer freezes at its starting point. Silence falls over the system—meaning the mechanisms are stationary and ready to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Logical One (The Strike)
&lt;/h3&gt;

&lt;p&gt;Now, we lift our finger and open the control tube. Atmospheric air rushes into the system instantly. The pressure inside the tubes and the pouch immediately equalizes with the outside environment. As soon as this happens, the collapsed rubber pouch snaps back to its rounded shape, driving our hammer forward. It strikes the string—and a note rings out.&lt;/p&gt;

&lt;p&gt;If we cover the hole with our finger again, the pump recreates the vacuum, external pressure flattens the pouch, and the hammer returns to its idle position. By opening and closing this tiny hole one after another, you can play a one-note melody.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Automation: The Birth of Punched Tape
&lt;/h2&gt;

&lt;p&gt;To avoid pressing the metal tube hole with a finger, let's take a long strip of paper and punch a sequence of holes into it beforehand. Now, we just roll this paper over the metal tip.&lt;/p&gt;

&lt;p&gt;The paper acts like a simple plug: as long as there is no hole, it blocks the metal opening and keeps the air out. The physics of this process is pure computer logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is there a hole in the paper?&lt;/strong&gt; It opens the tube of a specific key, system pressure equalizes, the pouch expands, and a strike occurs. This is a &lt;strong&gt;logical one (1)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No hole (blank paper)?&lt;/strong&gt; The inlet is closed, the pump maintains the vacuum, the pouch is flattened by external pressure, and the hammer stays still. This is a &lt;strong&gt;logical zero (0)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But how do you play a chord with multiple notes? We simply scale our system: we build a dedicated set with a pouch, tubes, and a hammer for every single key, and align their metal tips in a straight row.&lt;/p&gt;

&lt;p&gt;Now, to strike a chord, we punch multiple holes simultaneously along the same horizontal line (a single row) on a wide paper roll. When this row reaches the reading tracker bar, air rushes into all the open channels at once. At that exact microsecond, the corresponding pouches expand, and the hammers hit the strings at the exact same moment.&lt;/p&gt;

&lt;p&gt;Roll this paper up, and you get a punched roll—the first mass-produced removable data storage in history. Insert one roll, and it plays Johann Sebastian Bach. Swap it for another, and it plays Antonio Vivaldi. The pianola became the most straightforward, visual example of a device where swapping the paper tape completely changed the executed program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: From Punched Tape to Gigahertz
&lt;/h2&gt;

&lt;p&gt;If you look at the specs of a modern CPU, you usually see two main metrics: clock speed and word size (bitness). Our pianola explains both concepts in a heartbeat.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clock Rate (Hz)&lt;/strong&gt; is the scrolling speed of the roll. A pianola manages to pull the paper and change the state of its rubber pouches about 10 times per second (a clock rate of 10 Hz). In the mid-20th century, early electronic computers read lines from paper tapes at a rate of 1,000 times per second (1 kHz). A modern CPU switches its logical states at 5 billion times per second (5 GHz).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bitness&lt;/strong&gt; is the width of the paper tape. There are exactly 88 holes on a pianola's tracker bar—one for each piano key. The number of channels on a reading interface always indicates the bitness of the system. Since all 88 channels are read simultaneously rather than sequentially, the pianola is an &lt;strong&gt;88-bit processor&lt;/strong&gt;. It can process 88 bits of data in parallel within a single clock cycle. For comparison, a modern consumer CPU processes 64 bits per cycle.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Comparison Table: Pianola vs. Modern Computer
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pianola Component&lt;/th&gt;
&lt;th&gt;Computer Component&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Punched paper roll&lt;/td&gt;
&lt;td&gt;Storage (Flash memory / SSD / ROM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hole / No hole&lt;/td&gt;
&lt;td&gt;Data bit (&lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracker bar (row of holes)&lt;/td&gt;
&lt;td&gt;Data Bus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open / Closed air channel&lt;/td&gt;
&lt;td&gt;Transistor switch (ON / OFF)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vacuum pump and pedals&lt;/td&gt;
&lt;td&gt;Power Supply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roll scrolling speed&lt;/td&gt;
&lt;td&gt;Clock Rate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Great Ancestors and Shared Concepts
&lt;/h2&gt;

&lt;p&gt;Of course, Edwin Scott Votey, who created the commercial pianola in 1895, didn't invent this logic out of thin air. He stood on the shoulders of scientists and inventors from all over the world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;9th Century, Baghdad:&lt;/strong&gt; The Banu Musa brothers built the first hydropowered organ featuring a rotating cylinder. They were the first to prove that music could be encoded mechanically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1804, France:&lt;/strong&gt; Joseph Marie Jacquard used rigid punched cards with holes to automate weaving looms, enabling them to weave complex patterns automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1830s, England:&lt;/strong&gt; Charles Babbage adopted these identical Jacquard punched cards for his Analytical Engine design—the earliest blueprint for a mechanical computer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pianola became a brilliant evolution of these very ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Today, we write code using text characters in comfortable IDEs. A programmer, much like a composer, writes a software score that dictates what the computer should "play" and when. A dedicated compiler or interpreter translates our text into binary code. This process is essentially the same as creating punched rolls—mapping out zeros and ones on a medium.&lt;/p&gt;

&lt;p&gt;The foundational rules of computing have not changed. The pianola manipulated air pressure to move wooden hammers at a speed of 10 cycles per second. A modern CPU manipulates the movement of invisible electrons across a silicon crystal at billions of cycles per second. Yet, all this silicon magic operates under the exact same laws of binary logic that once controlled the keys of a self-playing musical instrument.&lt;/p&gt;




&lt;p&gt;Here's what it looks like in action. A pianola playing "The Entertainer" — a classic ragtime piece:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/07krQ661fok"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do you think?&lt;/strong&gt; What other vintage mechanisms or historical inventions do you find great for explaining complex modern tech? Let's talk in the comments below!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>architecture</category>
      <category>computerscience</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Django: History and Trends</title>
      <dc:creator>Yuri Penskikh</dc:creator>
      <pubDate>Wed, 01 Jul 2026 11:33:44 +0000</pubDate>
      <link>https://dev.to/gagatust/django-history-trends-and-the-burden-of-backward-compatibility-3g2a</link>
      <guid>https://dev.to/gagatust/django-history-trends-and-the-burden-of-backward-compatibility-3g2a</guid>
      <description>&lt;p&gt;Django is a Python development classic that has been helping build and maintain web projects of all scales for two decades. But how does this monolith cope with the challenges of modern async and SPA? And why has strict backward compatibility become both its greatest advantage and its main limitation?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The Django + DRF stack faces serious limits with typing and async. True async in Django is still not fully fixed, but the ecosystem adapts. Developers use new tools like django-modern-rest for better APIs, or just return to SSR + HTMX.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The Era of Web Spaghetti: How Django and Its Contemporaries Changed the Game
&lt;/h2&gt;

&lt;p&gt;The mid-2000s marked a global shift in web development. Almost simultaneously, a new generation of frameworks emerged: Ruby on Rails, Symfony, and Django. They brought order to the chaos of the lightweight web segment. They also offered a flexible alternative to the heavy Enterprise stack (Java, .NET).&lt;/p&gt;

&lt;p&gt;Spaghetti code—where HTML, SQL queries, and business logic were mixed in a single file—was replaced by a clear separation of concerns and the concept of rapid development.&lt;/p&gt;

&lt;p&gt;Django, like its competitors, offered developers everything they needed out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A built-in ORM&lt;/li&gt;
&lt;li&gt;An authentication subsystem&lt;/li&gt;
&lt;li&gt;An administrative panel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The web at that time operated on the classic Server-Side Rendering (SSR) model. The server received a request, fetched data from the database, rendered an HTML template, and returned the finished page to the browser. The community embraced pure dynamic typing. Programmers valued Python for its development speed and freedom from compilation.&lt;/p&gt;

&lt;p&gt;In this paradigm, the MTV (Model-Template-View) architecture fully met business needs. The built-in admin panel (Django Admin) became a technological breakthrough. Unlike Ruby on Rails, where Scaffolding generated physical code files, Django chose dynamic configuration. The admin panel read model metadata at application startup and generated the management interface on the fly.&lt;/p&gt;

&lt;p&gt;However, the decision to bind the admin panel, forms, and ORM via deep dynamic model introspection carried a hidden cost. Years later, this magic would turn into a serious barrier to full static typing support. Furthermore, the monolithic synchronous nature of the old ORM would significantly slow down the framework's transition to a modern asynchronous architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Great Async Irony: How DRF’s Creator Paved the Way for FastAPI
&lt;/h2&gt;

&lt;p&gt;By the mid-2010s, the explosive popularity of AngularJS and React turned Single Page Applications (SPAs) into the new industry standard. The backend became a mere data provider, serving pure JSON. This posed a serious challenge for monolithic Django: its built-in templates and forms suddenly became obsolete.&lt;/p&gt;

&lt;p&gt;Tom Christie saved the situation by creating the Django REST Framework (DRF). For many years, this tool secured Django's status as the primary web API in the Python ecosystem. However, DRF's architecture was designed long before modern language standards emerged—leaving it without built-in integration for type hints.&lt;/p&gt;

&lt;p&gt;Everything changed with the release of Python 3.5 in 2015. The language officially received native async support via &lt;code&gt;async/await&lt;/code&gt; syntax (&lt;code&gt;asyncio&lt;/code&gt;) and a type hinting standard. The rise of the &lt;code&gt;mypy&lt;/code&gt; static analyzer permanently changed Python development culture. Missing type hints in large projects gradually became bad practice, as codebases without them are difficult to refactor.&lt;/p&gt;

&lt;p&gt;Against this background, a major historical irony unfolded. The industry moved toward microservices, where Node.js and Go visibly outperformed Python in highly concurrent I/O operations. While Django Core developer Andrew Godwin tried to catch up with the trend by creating the ASGI async specification for Django, Tom Christie effectively laid a time bomb under his own creation. He founded Encode and released a new async ecosystem completely relying on fresh Python 3.5+ features: the Uvicorn server, the Starlette lightweight framework, and HTTPX.&lt;/p&gt;

&lt;p&gt;Starlette soon became the foundation for FastAPI. It combined Tom Christie's async groundwork with strict typing and end-to-end validation via Pydantic. In this light, classic Django, along with the aging DRF, suddenly looked like a bulky tech stack from the past.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Technological Deadlock: What Is Wrong with the Old Stack Today?
&lt;/h2&gt;

&lt;p&gt;Caught in the new reality, the Django + DRF combination began rapidly losing ground for modern API design. There are several compelling reasons for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low performance of DRF serializers.&lt;/strong&gt; Pure Python validation with numerous internal calls and dynamic introspection runs slowly. Modern tools powered by Rust cores (like Pydantic v2) parse and validate data several times faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Violation of the Single Responsibility Principle (SRP).&lt;/strong&gt; The &lt;code&gt;ModelSerializer&lt;/code&gt; class is tightly coupled with the ORM. It simultaneously describes the request/response schema, validates business logic, and manages data persistence. In large projects, this often creates bloated "God Objects" spanning thousands of lines of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of native async support in DRF.&lt;/strong&gt; The framework's architecture remains synchronous: serializer and view methods do not support the &lt;code&gt;await&lt;/code&gt; keyword. When running a project via an ASGI server, Django's async core is forced to run DRF's synchronous code inside a thread pool, which completely negates the benefits of async.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difficulties with message broker integration.&lt;/strong&gt; Django's architecture is isolated within the classic request-response lifecycle. There is no built-in mechanism to run long-lived processes. The &lt;code&gt;django.tasks&lt;/code&gt; module introduced in Django 6.0 only defines an interface but lacks a production-ready worker. To work with RabbitMQ or Kafka, developers still have to deploy Celery or write custom management commands.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Evolution in Spite of Obstacles: How &lt;code&gt;django-modern-rest&lt;/code&gt; Changes the Game
&lt;/h2&gt;

&lt;p&gt;The community quickly realized that waiting for a global overhaul of DRF was pointless and began creating alternative solutions. Following the popular Django Ninja, &lt;code&gt;django-modern-rest&lt;/code&gt; (DMR) became the technological answer to modern challenges.&lt;/p&gt;

&lt;p&gt;The main conceptual difference of DMR is the complete separation of the API layer from business models, thanks to a pluggable architecture. Instead of heavy DRF mechanisms, developers can choose their validation engine: the familiar Pydantic or the ultra-performant &lt;code&gt;msgspec&lt;/code&gt;, which accelerates JSON processing significantly.&lt;/p&gt;

&lt;p&gt;DMR generates accurate OpenAPI specifications directly from type hints and strictly validates data at runtime. This provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native async support (ASGI) without extra layers&lt;/li&gt;
&lt;li&gt;Out-of-the-box auto-documentation&lt;/li&gt;
&lt;li&gt;Advanced API testing (e.g., via integration with Schemathesis)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the same time, standard Django Views remain under the hood. For developers, this means the best of both worlds: FastAPI-level speed and strict typing without losing the mature Django ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The Burden of Backward Compatibility: Why You Can't Just Rewrite Everything
&lt;/h2&gt;

&lt;p&gt;Why can't Django Core developers rebuild the system to match new standards overnight? The answer is simple: businesses value the framework precisely for its stability. Radical changes would break thousands of third-party packages. By breaking version continuity, Django risks repeating the painful, drawn-out experience the IT industry faced when moving from Python 2 to Python 3.&lt;/p&gt;

&lt;p&gt;Consequently, async features are introduced evolutionarily—from release to release. In practice, however, this approach remains a compromise. Under the hood, the framework's async interfaces are still forced to rely on synchronous threads when interacting with legacy components. Additionally, a massive array of third-party plugins, the DRF ecosystem, and the built-in admin panel remain tightly bound to a synchronous architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Will the Old Timer Keep Fighting?
&lt;/h2&gt;

&lt;p&gt;Is Django dying? Definitely not. The framework is going through a prolonged and painful transformation. True, it is no longer the default choice for lightweight microservices. However, the emergence of tools like &lt;code&gt;django-modern-rest&lt;/code&gt; clearly proves that the ecosystem can adapt to modern challenges despite the conservative limitations of its core.&lt;/p&gt;

&lt;p&gt;Moreover, there is a distinct "SPA fatigue" visible in the industry today. Maintaining a bloated frontend just for simple interfaces costs businesses a lot of money. The industry is returning to Server-Side Rendering (SSR) and lightweight HTMX. In this new reality, Django's classic architecture is once again in high demand.&lt;/p&gt;

&lt;p&gt;Ultimately, strict backward compatibility—which once seemed like a limitation—has turned into the framework's main commercial advantage. Projects written ten years ago continue to run stably, generate revenue, and upgrade smoothly to current versions without needing a complete rewrite.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Stop Saying Python Iterators Are Eager</title>
      <dc:creator>Yuri Penskikh</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:40:09 +0000</pubDate>
      <link>https://dev.to/gagatust/stop-saying-python-iterators-are-eager-4db0</link>
      <guid>https://dev.to/gagatust/stop-saying-python-iterators-are-eager-4db0</guid>
      <description>&lt;p&gt;As a backend developer, I sometimes help companies evaluate candidates by reviewing their recorded technical interviews. However, over time, I’ve noticed a deeply ingrained misconception. When discussing memory management or data streaming, many developers explicitly state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Iterators in Python are inherently eager. If you want true lazy loading or lazy evaluation, you have to use generators and the yield keyword."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This misconception is common. Many popular bootcamps and online courses introduce &lt;strong&gt;lazy evaluation exclusively through generators&lt;/strong&gt;. Custom class-based iterators are usually skipped or dismissed as boilerplate-heavy OOP theory rarely used in production Python.&lt;/p&gt;

&lt;p&gt;This confusion is further reinforced by &lt;strong&gt;two common educational simplifications&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The List vs. Generator Expression Analogy:&lt;/strong&gt; Beginners are taught that square brackets &lt;code&gt;[...]&lt;/code&gt; (list comprehensions) are eager and take up memory, while parentheses &lt;code&gt;(...)&lt;/code&gt; (generator expressions) are lazy. This often creates a false binary mental model: &lt;strong&gt;"generators = lazy, everything else = eager."&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Standard "Textbook" Examples:&lt;/strong&gt; When courses demonstrate a custom iterator, they usually write a basic class that accepts an already fully loaded list in its &lt;code&gt;__init__&lt;/code&gt; and simply increments an index in &lt;code&gt;__next__&lt;/code&gt;. While this is valid for in-memory data, it leads developers to assume that custom iterators inherently require loading all data upfront.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In reality, &lt;strong&gt;generators are a specialized language feature designed to implement the iterator protocol automatically&lt;/strong&gt;. They comply with the exact same interface (&lt;code&gt;__iter__&lt;/code&gt; and &lt;code&gt;__next__&lt;/code&gt;). A generator is lazy not because of some magical property of the &lt;code&gt;yield&lt;/code&gt; keyword, but simply because it adheres to this underlying contract.&lt;/p&gt;

&lt;p&gt;To show that &lt;strong&gt;custom iterators can be lazy&lt;/strong&gt; without using any generators or &lt;code&gt;yield&lt;/code&gt; keywords, I’ve put together a lightweight and reproducible benchmark.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧪 The Experiment: Proving Lazy Loading with Custom Iterators
&lt;/h3&gt;

&lt;p&gt;Suppose we need to read a database export file (&lt;code&gt;test_users_db.txt&lt;/code&gt;) containing 1_000_000 rows of user data. We want to iterate over these users but stop immediately once we find a specific record (at &lt;code&gt;HALF_ROWS=500_000&lt;/code&gt;). If custom iterators were truly "eager," our lazy implementation would significantly increase RAM consumption.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Please note:&lt;/strong&gt; I’m using a local text file here as a simplified data source. However, in a production system, this exact same pattern is used to &lt;strong&gt;stream rows from a heavy SQL database cursor&lt;/strong&gt;, fetch paginated data from an external HTTP API, or consume messages from a queue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's look at the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;FILE_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_users_db.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TOTAL_ROWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;
&lt;span class="n"&gt;HALF_ROWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOTAL_ROWS&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_test_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOTAL_ROWS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,User_Name_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDTO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="c1"&gt;# --- LAZY IMPLEMENTATION ---
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LazyUserGateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LazyUserIterator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;LazyUserIterator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LazyUserIterator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LazyUserIterator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__next__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UserDTO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Step-by-step stream: reading only one line into memory at a time
&lt;/span&gt;        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;StopIteration&lt;/span&gt;

        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;UserDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# --- EAGER IMPLEMENTATION ---
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EagerUserGateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EagerUserIterator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EagerUserIterator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EagerUserIterator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# The readlines() call forces Python to build a massive list of strings in RAM upfront
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__iter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EagerUserIterator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__next__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;UserDTO&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;StopIteration&lt;/span&gt;

        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;UserDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# --- BENCHMARKS ---
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_eager_gateway&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FILE_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EagerUserGateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HALF_ROWS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Eager] Final reached: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_traced_memory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_lazy_gateway&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FILE_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LazyUserGateway&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HALF_ROWS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Lazy]  Final reached: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_traced_memory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;tracemalloc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;peak&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;generate_test_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FILE_PATH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;--- Memory Benchmark Running ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;peak_eager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test_eager_gateway&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt; Peak Memory (EagerUserGateway): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;peak_eager&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; MB&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;peak_lazy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test_lazy_gateway&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&amp;gt; Peak Memory (LazyUserGateway):  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;peak_lazy&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; MB&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;FILE_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;FILE_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📊 Benchmark Results
&lt;/h3&gt;

&lt;p&gt;Running the script yields the following terminal output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- Memory Benchmark Running ---
[Eager] Final reached: UserDTO(id=500000, name='User_Name_500000')
-&amp;gt; Peak Memory (EagerUserGateway): 69.97 MB

[Lazy]  Final reached: UserDTO(id=500000, name='User_Name_500000')
-&amp;gt; Peak Memory (LazyUserGateway):  0.15 MB

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This represents a &lt;strong&gt;~500x reduction in RAM consumption&lt;/strong&gt; (dropping from ~69.97MB to 0.15MB). While the eager implementation scales linearly with the dataset size, the &lt;strong&gt;lazy iterator maintains a flat, near-zero memory footprint&lt;/strong&gt; regardless of the row count.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧠 Iterators vs. Generators: Contract vs. Convenience
&lt;/h3&gt;

&lt;p&gt;In practice, &lt;strong&gt;generators are the industry standard for a reason&lt;/strong&gt;. In most daily tasks, writing a quick generator function with &lt;code&gt;yield&lt;/code&gt; is much faster, safer, and cleaner than writing custom iterator classes. The generator object itself handles the boilerplate, implicitly saves execution state, and natively facilitates resource cleanup.&lt;/p&gt;

&lt;p&gt;But as engineers, we shouldn't confuse &lt;strong&gt;implementation-specific capabilities&lt;/strong&gt; with a &lt;strong&gt;structural interface contract&lt;/strong&gt;, especially when designing architectural boundaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;An Iterator is just a contract (interface).&lt;/strong&gt; It guarantees that data can be traversed step-by-step via &lt;code&gt;__next__&lt;/code&gt;. How that data is managed — whether the iterator eagerly fetches it upfront, lazily streams it from an API, or simply acts as a pointer to data passed to it from the outside — is &lt;strong&gt;entirely up to the developer&lt;/strong&gt; who implements it. An iterator doesn't even need to own or fetch data; it just drives the traversal logic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A Generator is a specialized language feature&lt;/strong&gt; that auto-implements this protocol while supporting additional lifecycle and communication methods (via &lt;code&gt;.send()&lt;/code&gt;, &lt;code&gt;.throw()&lt;/code&gt;, and &lt;code&gt;.close()&lt;/code&gt;). For basic data streaming, you don't need this additional complexity. A custom iterator class gives you a clean, one-way contract strictly limited to data traversal, keeping your architectural boundaries &lt;strong&gt;well-defined&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom class-based iterators deliver the exact same polymorphic interface. They aren't meant to replace generators everywhere. Instead, they prove that &lt;strong&gt;lazy evaluation is an architectural design choice&lt;/strong&gt;, not a magic trick powered strictly by &lt;code&gt;yield&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏁 Conclusion
&lt;/h3&gt;

&lt;p&gt;The "iterators are eager" take is just a byproduct of simplified educational content. Diving deeper into core protocols helps us write much better, more predictable architectural layers.&lt;/p&gt;




&lt;p&gt;💬 &lt;strong&gt;What about you?&lt;/strong&gt; Have you ever heard that custom Python iterators are inherently eager?&lt;/p&gt;




</description>
      <category>python</category>
      <category>performance</category>
      <category>discuss</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
