Python 3.7 Features: Dataclasses and More

python dev

Python 3.7 dropped in June 2018 with some fantastic features. Dataclasses alone are worth the upgrade. Let’s explore what’s new.

Dataclasses: The Headline Feature

Before dataclasses:

class Point:
    def __init__(self, x, y, z=0):
        self.x = x
        self.y = y
        self.z = z
    
    def __repr__(self):
        return f'Point(x={self.x}, y={self.y}, z={self.z})'
    
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y and self.z == other.z

With dataclasses:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float
    z: float = 0

That’s it. __init__, __repr__, __eq__ are generated automatically.

Customization Options

@dataclass(frozen=True)  # Immutable
class Config:
    host: str
    port: int = 8080

@dataclass(order=True)  # Adds comparison operators
class Version:
    major: int
    minor: int
    patch: int

Default Factories

For mutable defaults:

from dataclasses import dataclass, field

@dataclass
class ShoppingCart:
    items: list = field(default_factory=list)
    created_at: datetime = field(default_factory=datetime.now)

Post-Init Processing

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)
    
    def __post_init__(self):
        self.area = self.width * self.height

Converting to Dict/Tuple

from dataclasses import asdict, astuple

point = Point(1, 2, 3)
asdict(point)   # {'x': 1, 'y': 2, 'z': 3}
astuple(point)  # (1, 2, 3)

Built-in breakpoint()

Debug without importing:

# Before
import pdb; pdb.set_trace()

# Now
breakpoint()

Respects PYTHONBREAKPOINT environment variable:

PYTHONBREAKPOINT=0 python script.py  # Disable
PYTHONBREAKPOINT=ipdb.set_trace python script.py  # Use ipdb

Typing Improvements

Forward References as Strings

class Node:
    def __init__(self, children: 'List[Node]'):  # String for forward ref
        self.children = children

With from __future__ import annotations:

from __future__ import annotations

class Node:
    def __init__(self, children: List[Node]):  # No quotes needed
        self.children = children

typing.get_type_hints()

Resolve forward references at runtime:

from typing import get_type_hints

hints = get_type_hints(Node.__init__)

Dictionary Order Preserved

Now guaranteed by language spec (was CPython implementation detail in 3.6):

d = {'a': 1, 'b': 2, 'c': 3}
list(d.keys())  # Always ['a', 'b', 'c']

Context Variables

For propagating context through async code:

from contextvars import ContextVar

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

async def process_request():
    token = request_id.set('abc123')
    await do_work()  # request_id is available
    request_id.reset(token)

namedtuple Improvements

from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float
    z: float = 0.0  # Defaults now supported

Time Functions Get Nanoseconds

import time

time.time_ns()              # Nanosecond timestamp
time.perf_counter_ns()      # High-resolution counter
time.process_time_ns()      # CPU time

Faster Module Attribute Access

Modules can now define __getattr__ and __dir__:

# mymodule.py
def __getattr__(name):
    if name == 'lazy_value':
        return compute_expensive_value()
    raise AttributeError(f'module has no attribute {name}')

Performance Improvements

Python 3.7 is faster:

These are free wins—no code changes required.

When to Upgrade

Do upgrade if:

Wait if:

Migration Tips

  1. Test thoroughly—some behaviors changed
  2. Update dependencies first
  3. Consider pyupgrade for automatic syntax updates
  4. Gradually adopt new features

Dataclasses vs Alternatives

Dataclasses vs namedtuple:

Dataclasses vs attrs:

Dataclasses vs Pydantic:

Final Thoughts

Python 3.7 is a quality release. Dataclasses alone will save you hundreds of lines of boilerplate.

The improvements are incremental but meaningful. Upgrade when you can—your code will thank you.


Less boilerplate, more productivity.

All posts