The Core Problem with None Spreads None seems harmless at first. It acts as a quick, convenient placeholder when data is missing or operations fail. However, once `None` slips into your application's domain core, it acts like a virus. Every function, class, and method that interacts with that data must run continuous defensive validation checks. This architecture pattern leads to highly verbose, unreadable code. The issue is not that raw, external data contains empty values. The issue is letting that messy data penetrate your internal business logic without strict validation. To clean up your architecture, you must push safety checks to the boundaries of your system and keep your core domain strict and predictable. Prerequisites To get the most out of this tutorial, you should have a solid grasp of intermediate Python concepts. You should be familiar with object-oriented programming (OOP), standard error-handling structures, and typing paradigms. Familiarity with Data Classes is highly recommended. Key Libraries & Tools - **Python Standard Library**: This refactoring relies heavily on built-in tools like `dataclasses` (specifically `field` and `default_factory`) and typing tools like `Protocol`. - **Returns Package**: A popular community package that provides functional result types, though standard exceptions are preferred for standard Python codebases. Refactoring Away From None Let's break down the primary strategies to clean up your architecture by refactoring a messy drone delivery system. Use Better Defaults If a `None` value represents an empty state, nothing to do, or a disabled feature, replace it with a valid default value. Instead of checking for `None` before iterating, use an empty collection. ```python from dataclasses import dataclass, field @dataclass class Delivery: # Bad: avoid_zones: list[GeoFence] | None = None # Better: Use an empty list by default avoid_zones: list[GeoFence] = field(default_factory=list) ``` Push Validation to the Edge Separate raw, messy data structures at your application's boundaries from your strict internal domain objects. Validate data once as it enters your system, then pass around guaranteed types. ```python @dataclass(frozen=True) class ReadyDrone: drone_id: str location: Coordinates battery_level: int max_wind_speed: float def prepare_drone(telemetry: RawTelemetry) -> ReadyDrone: if telemetry.location is None: raise ValueError("Drone has no GPS location") if telemetry.battery is None: raise ValueError("Drone has no battery data") return ReadyDrone( drone_id=telemetry.drone_id, location=telemetry.location, battery_level=telemetry.battery, max_wind_speed=telemetry.max_wind_speed ) ``` Replace None Returns with Exceptions Instead of returning `None` when a function cannot fulfill its purpose, raise an explicit, domain-specific exception. This forces callers to handle the failure explicitly or let it propagate, avoiding silent errors. ```python class RouteRejected(Exception): pass def assign_delivery(drone: ReadyDrone) -> Route: if drone.battery_level < 20: raise RouteRejected("Battery level too low") return Route(destination=drone.location) ``` The Null Object Pattern When "doing nothing" is a valid and expected behavior, implement a Null Object. By implementing a common interface that performs no operation, you remove the need for repetitive conditional checks. ```python from typing import Protocol class Diagnostics(Protocol): def record(self, message: str) -> None: ... class ActiveDiagnostics: def record(self, message: str) -> None: print(f"LOG: {message}") class NullDiagnostics: def record(self, message: str) -> None: pass # Do absolutely nothing ``` Syntax Notes Using `Protocol` from the `typing` module allows you to implement structural subtyping (duck typing) in Python. Your classes do not need to explicitly inherit from the protocol class; they only need to implement the defined methods. This makes it incredibly easy to swap a real logging class for a `NullDiagnostics` helper. Practical Examples These patterns apply to many software domains outside of drone dispatch systems: - **API Integration**: Translating loose, missing-key JSON payloads into strict domain entities using Pydantic or dataclasses at the controller layer. - **Feature Flagging**: Using a Null Feature Flag client that defaults all flags to `False` in local development without injecting mock checks into your code. Tips & Gotchas - **Do Not Abuse Result Objects**: While functional programming structures like `Result` types (success or failure wrappers) are popular in Rust, they can confuse traditional Python developers. Standard custom exceptions are much more idiomatic. - **Failing Fast**: Always fail fast. Crash immediately at your API boundaries rather than letting a corrupted state slip deep into your execution thread.
Data Classes
Products
Feb 2023 • 2 videos
High activity month for Data Classes. ArjanCodes among the most active voices, with 2 videos across 1 sources.
Feb 2023
Sep 2025 • 1 videos
Steady coverage of Data Classes. ArjanCodes contributed to 1 videos from 1 sources.
Sep 2025
Jul 2026 • 1 videos
Steady coverage of Data Classes. ArjanCodes contributed to 1 videos from 1 sources.
Jul 2026
TL;DR
ArjanCodes (3 mentions) advocates for using Data Classes to achieve modularity with minimal overhead in 'I Tried SOLID Principles in Python…,' though 'Attrs, Pydantic, or Python Data Classes?' highlights that they often lack the complex validation features found in third-party alternatives.
- 5 days ago
- Sep 26, 2025
- Feb 17, 2023
- Feb 14, 2023