Add a fields interface

Can we add a common interface to replace all of: NamedTuple._dict(), dataclassses.fields(), attrs.fields(), pydantic.BaseModel.model_fields(), etc?

The equivalent for NamedTuple._replace(), dataclasses.replace(), attrs.evolve(), etc. has been solved with copy.replace() and __replace__ in Python 3.13.

Why? It’s pretty common to want to write generic code in the form:

def map_[T](o: T, f: Callable[[T], T]) -> T:
    if isinstance(o, list):
        return [map_(v, f) for v in o]
    # ditto for tuple, set, frozenset, dict, then,
    if has_fields(o):
        kwargs = {k: map_(getattr(o, field.name), f) for field in fields(o)}
        return copy.replace(o, **kwargs)
    return o

Recently I’ve wanted to do something like this pattern for a pretty printing lib and for generically anonymising data in an application.

Interestingly, there is a de facto (if unused) standard if you squint a bit - if you implement __dataclass_fields__ to return a dict[str, dataclasses.Field], both dataclasses.is_dataclass() and dataclasses.fields() will work - see make_attrs_class_dataclass_compatible.py. This small decorator seemingly makes pydantic play well with attrs classes (I’ve yet to find an edge case). So maybe we should just be telling library maintainers to implement __dataclass_fields__

I’m not sure entirely what subset of existing Fields classes fieldslib.Field should contain, a name and a type would suffice for most things. Before anyone jumps in, annotationlib.get_annotations() doesn’t cut it as it doesn’t handle inheritance, __dict__ drags up all sorts of non-field stuff.

I’m expecting someone has proposed this before, but I can’t find it.