field() options and metadata

dataclasses · Practical · 10 min

Secrets in reprs, caches in comparisons and per-field config are common needs field() solves.

dataclasses.field() customises one field. Its parameters:

  • default= / default_factory=: the default value or a factory (never both).
  • init=False: not a constructor parameter (set it in __post_init__ or give a default).
  • repr=False: hide from __repr__. Use it for passwords, tokens and huge blobs, so they don't leak into logs.
  • compare=False: exclude from __eq__ and ordering, for caches, timestamps or descriptions.
  • hash=None|True|False: include in __hash__; None (default) follows compare.
  • kw_only=True: keyword-only parameter (3.10+).
  • metadata=: a read-only mapping for your own use; dataclasses ignores it. Libraries and your code read it via fields(), which is how you build per-field config such as column names, units or validation rules.

dataclasses.fields(obj_or_class) returns Field objects with .name, .type, .default and .metadata, letting you write generic code: serialisers, CSV writers, form builders.

field() must be used as a class attribute in the dataclass body; using it elsewhere has no meaning.

Example

from dataclasses import dataclass, field, fields

@dataclass
class DbConfig:
    host: str = field(metadata={"env": "DB_HOST"})
    port: int = field(default=5432, metadata={"env": "DB_PORT"})
    password: str = field(default="", repr=False, metadata={"env": "DB_PASSWORD"})
    _cache: dict = field(default_factory=dict, repr=False, compare=False)

cfg = DbConfig("db.local", password="s3cret")
print(cfg)                          # DbConfig(host='db.local', port=5432)  (no password)
cfg._cache["x"] = 1
print(cfg == DbConfig("db.local", password="s3cret"))   # True: _cache not compared

def from_env(env: dict[str, str]) -> DbConfig:
    kwargs = {f.name: f.type(env[f.metadata["env"]])
              for f in fields(DbConfig) if f.metadata.get("env") in env}
    return DbConfig(**kwargs)

print(from_env({"DB_HOST": "prod", "DB_PORT": "6543"}))  # DbConfig(host='prod', port=6543)

Remember

  • field(repr=False) hides secrets; compare=False excludes fields from ==
  • metadata= attaches your own per-field information
  • fields() lets generic code inspect names, types and metadata

Watch out for

  • Printing or logging config objects whose secret fields aren't repr=False.

Check yourself

  1. What does dataclasses do with a field's metadata mapping?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/dataclasses.html#dataclasses.field