DS DevShelfHub Projects · AI tools
Articles / 3 Modern Python Features That Replace Hundreds of Lines of Boilerplate

AI Engineering

3 Modern Python Features That Replace Hundreds of Lines of Boilerplate

By DevShelfHub

Three Python features in the standard interpreter that most developers never use — the match statement for structural pattern matching, @dataclass for killing class boilerplate with frozen/slots/kw_only flags, and positional-only / keyword-only parameters for safer APIs. Plus how they compose, the mutable-default trap that bites dataclass users, and exactly when to reach for each.

3 Modern Python Features That Replace Hundreds of Lines of Boilerplate

Introduction

Python has shipped a steady stream of language features over the past few releases that quietly change how you write code — if you actually use them. Most developers don’t. The syntax exists, the docs explain it, and the everyday Python being written looks like Python from 2018.

Three features stand out as worth knowing in 2026. They’re not exotic — they’re in the standard interpreter, well-supported by type checkers and linters, and immediately useful. Match statements, dataclasses, and positional-or-keyword-only parameters. Here’s what each one does, when to reach for it, and the common mistakes when you start using them.

📚 Table of contents

  • 1. The match statement (structural pattern matching)
  • 2. @dataclass — the boilerplate killer
  • 3. Positional-only and keyword-only parameters
  • How they compose together
  • Common mistakes
  • FAQs

1. The match statement (structural pattern matching)

Added in Python 3.10. Looks like a switch; does much more. The simple form replaces nested if/elif chains:

Python
def describe(status):
    match status:
        case "success":
            return "operation completed"
        case "error":
            return "an error occurred"
        case "pending":
            return "still in progress"
        case _:
            return "unknown status"

Same as a switch. The interesting part is structural matching — you can pattern-match against shapes, not just values:

Python
def process(data):
    match data:
        case 0:
            return "zero"
        case 1 | 2 | 3:                     # OR of values
            return f"small: {data}"
        case [first, second]:               # exactly 2-element list
            return f"pair: {first}, {second}"
        case [first, *rest]:                # list with at least 1
            return f"first={first} rest={rest}"
        case {"name": name, "age": age}:    # dict with these keys
            return f"{name} is {age}"
        case str():                         # any string
            return f"string: {data}"
        case _:
            return "unknown"

Patterns can be values, OR-combinations with |, list shapes, dict shapes, type checks, and combinations of all the above. Captured variables (first, second, name, age) bind during the match and are usable inside the case body. Cases are tried in order — put more specific patterns first.

Where it pays off: parsing structured data, walking ASTs, dispatching on JSON shapes, handling different message types in an event system. Anywhere you’d previously write if isinstance(x, dict) and "name" in x and "age" in x: — match replaces it with one readable line.

2. @dataclass — the boilerplate killer

Old-style data classes:

Python (the old way)
class User:
    def __init__(self, id, name, roles=None):
        self.id = id
        self.name = name
        self.roles = roles if roles is not None else []

    def __repr__(self):
        return f"User(id={self.id!r}, name={self.name!r}, roles={self.roles!r})"

    def __eq__(self, other):
        if not isinstance(other, User):
            return NotImplemented
        return (self.id, self.name, self.roles) == (other.id, other.name, other.roles)

Same thing with @dataclass:

Python (modern)
from dataclasses import dataclass, field

@dataclass(frozen=True)
class User:
    id: int
    name: str
    roles: list[str] = field(default_factory=list)

Six lines instead of fourteen, with stronger guarantees. The decorator generates __init__, __repr__, __eq__, and (because of frozen=True) __hash__ for you. Attempting to mutate user.name = "x" on a frozen dataclass raises an exception.

Important gotcha: never write roles: list[str] = []. The mutable default sticks to the class and silently shares state across instances — the same trap as mutable default arguments. Use field(default_factory=list) instead.

Useful flags on the decorator:

  • frozen=True — immutable instances, hashable.
  • order=True — generate __lt__, __le__, etc.
  • slots=True — use __slots__ for lower memory.
  • kw_only=True (3.10+) — force keyword-only construction.

Dataclasses pair extremely well with the match statement — you can pattern-match on dataclass shapes directly:

Python
@dataclass
class Product:
    id: int; name: str; price: float; in_stock: bool

def categorize(p):
    match p:
        case Product(price=0):
            return "free"
        case Product(price=p) if p > 1000:
            return "expensive"
        case Product(in_stock=False):
            return "unavailable"
        case _:
            return "regular"

3. Positional-only and keyword-only parameters

Two underused features of Python function signatures that pay off most when you’re writing library code. Use / to mark parameters as positional-only, * to mark them as keyword-only:

Python
def greet(name, /, greeting="Hello", *, punctuation="!"):
    return f"{greeting}, {name}{punctuation}"

greet("Alice")                            # OK
greet("Bob", "Hi")                        # OK — greeting positionally
greet("Charlie", greeting="Hey")          # OK
greet("Dana", punctuation=".")            # OK
greet(name="Eve")                         # TypeError — name is positional-only
greet("Frank", "Hello", "!")              # TypeError — punctuation is keyword-only

Three rules:

  • Everything before / must be passed positionally.
  • Everything between / and * can be passed either way.
  • Everything after * must be passed as a keyword argument.

Why bother?

  • API stability — positional-only means you can rename the internal parameter later without breaking callers. Keyword-only means callers can’t accidentally swap two arguments.
  • Readabilityopen("f", "r") reads fine positionally; complex_function(x=1, y=2, z=3) reads better with keyword arguments forced.
  • Consistency with built-ins — many Python built-ins already enforce positional-only (try pow(base=2, exp=3)).
  • Documentation — the signature itself tells the caller how to call it. No guesswork.

Common pattern in production: positional-only for arguments without meaningful names (x, y, index); keyword-only for boolean flags (force=True, strict=False) where a bare positional True/False would be cryptic.

How they compose together

These three features layer cleanly:

Python
from dataclasses import dataclass

@dataclass(frozen=True, slots=True, kw_only=True)
class Event:
    type: str
    payload: dict

def handle(event: Event, /, *, verbose: bool = False) -> str:
    match event:
        case Event(type="login", payload={"user": user}):
            return f"login: {user}"
        case Event(type="purchase", payload={"amount": amount, **rest}):
            return f"purchase: ${amount}" + (f" extras={rest}" if verbose else "")
        case Event(type=t):
            return f"unhandled: {t}"

Frozen dataclass for the event, structural pattern match on its shape, positional-only first parameter so you can rename it later, keyword-only verbose flag for readability. Idiomatic modern Python in eight lines.

❌ Common mistakes

  • Mutable defaults on a dataclass field. Use field(default_factory=list), not = [].
  • Ordering match cases wrong. The most specific pattern must come first; the catch-all (case _) must be last.
  • Using match for simple value dispatch where a dict lookup would be clearer.
  • Adding positional-only enforcement to internal functions where it gains nothing and just confuses readers.
  • Forgetting that frozen dataclasses are hashable. If you mutate something inside (e.g. a list field), the hash silently goes wrong.
  • Skipping slots=True on dataclasses you instantiate in tight loops — meaningful memory savings.

💡 Pro tips

  • Pair dataclasses with Pydantic for API boundaries. Dataclasses for internal types, Pydantic for validated inputs.
  • Use kw_only=True on dataclasses with more than 4 fields. Caller code stays readable.
  • For exhaustive matches on enum or Literal types, the typing-extensions assert_never trick gives compile-time exhaustiveness via mypy/pyright.
  • Type checkers (mypy, pyright, pyrefly) understand all three features. Configure them on your project to catch misuse early.
  • Run ruff check — many rules nudge you toward these features (e.g. flag mutable defaults).
  • For old codebases on Python 3.9 or earlier, plan an upgrade. The DX improvement is real.

Conclusion

Three features, all in the standard library, all immediately useful. Match statement collapses a class of if/elif chains and shines for structured data. @dataclass deletes 80% of the boilerplate class code most Python projects ship with. Positional/keyword-only parameters make your APIs both safer and clearer for callers.

Adopt them in your next project. Six months later your old code will feel verbose and you won’t be sure why — that’s the sign the new patterns took.

3 Modern Python Features That Replace Hundreds of Lines of Boilerplate FAQ

Is match faster than if/elif chains?

Marginally faster for value matching, similar for complex patterns. The win is readability and correctness, not raw speed. Don’t optimize for performance with match; optimize for code that’s harder to misread.

Dataclasses vs Pydantic vs attrs?

Dataclasses ship with Python, no runtime validation. Pydantic validates inputs at runtime — perfect for API boundaries. attrs is the pre-dataclass library that’s still excellent for advanced use cases (converters, validators, slots-by-default). For most internal types, dataclasses are sufficient. For data crossing your API surface, use Pydantic.

Can match guard with conditions?

Yes — use a case Pattern if condition: guard. Useful for things like case Product(price=p) if p > 1000:. Combines value matching with arbitrary predicates.

When should I avoid match?

Two cases. (1) You have a simple value-to-value mapping with no logic — a dict lookup is cleaner. (2) The match is short enough that an if/elif chain reads better; match shines when patterns are structural or compound.

Are positional-only params overkill for app code?

Mostly yes. They’re a library-author tool. In application code, use them sparingly — mainly for arguments without meaningful names. Keyword-only flags, however, are useful even in app code.

What about pattern matching on custom classes?

Yes — define __match_args__ on your class (or use dataclasses which generate it for you). Then case MyClass(x, y) destructures positionally; case MyClass(x=1, y=2) destructures by keyword.