Welcome to part six of our Python 3 features series! Today we’re looking at two gems: dataclasses for boilerplate-free data containers, and contextvars for managing context in async code.

1. Dataclasses

Before Python 3.7, you’d write classes like this if you wanted to bundle data:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

That’s a lot of typing for two fields. Enter dataclasses (PEP 557):

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p = Point(3.5, 7.2)
print(p)  # Point(x=3.5, y=7.2)

All of that __init__, __repr__, and comparison methods come for free.

Handy Features

  • Default values:

    @dataclass
    class User:
        name: str
        active: bool = True
    
  • Immutability with frozen=True:

    @dataclass(frozen=True)
    class Color:
        r: int
        g: int
        b: int
    c = Color(255, 0, 0)
    # c.r = 128  # ❌ can't change!  
    
  • Field customization with field():

    from dataclasses import field
    
    @dataclass
    class Item:
        name: str
        price: float = field(default=0.0, repr=False)
    
  • Convert to dict easily:

    from dataclasses import asdict
    print(asdict(p))  # {'x': 3.5, 'y': 7.2}
    

2. Context Variables: Keeping State in Async Land

When you run async tasks in parallel, global vars can clash. contextvars (PEP 567) fixes that by giving each task its own context.

import asyncio
from contextvars import ContextVar

current_user: ContextVar[str] = ContextVar('current_user')

async def handle_request(name):
    token = current_user.set(name)
    await asyncio.sleep(0.1)
    print(f"Handling request for {current_user.get()}")
    current_user.reset(token)

async def main():
    await asyncio.gather(
        handle_request('alice'),
        handle_request('bob'),
    )

asyncio.run(main())
# Handling request for alice
# Handling request for bob

Each coroutine sees its own current_user without stomping on others.


That’s it for dataclasses and contextvars. In our final part, we’ll explore the walrus operator and positional-only params to wrap up this series. Happy coding!