On Fri, Mar 12, 2021, 11:55 PM Eric V. Smith <eric@trueblade.com> wrote:
There have been many requests to add keyword-only fields to dataclasses.
These fields would result in __init__ parameters that are keyword-only.
As long as I'm doing this, I'd like to add positional-only fields as well.

Have there also been requests for positional-only fields?

The more I digest this idea, the more supporting positional-only fields sounds like a bad idea to me. The motivation for adding positional-only arguments to the language was a) that some built-in functions take only positional arguments, and there was no consistent way to document that and no way to match their interface with pure Python functions, b) that some parameters have no semantic meaning and making their names part of the public API forces library authors to maintain backwards compatibility on totally arbitrary names, and c) that functions like `dict.update` that take arbitrary keyword arguments must have positional-only parameters in order to not artificially reduce the set of keyword arguments that may be passed (e.g., `some_dict.update(self=5)`).

None of these cases seem to apply to dataclasses. There are no existing dataclasses that take positional-only arguments that we need consistency with. Dataclasses' constructors don't take arbitrary keyword arguments in excess of their declared fields. And most crucially, the field names become part of the public API of the class. Dataclass fields can never be renamed without a risk of breaking existing users. Taking your example from the other thread:

```
@dataclasses.dataclass
class Comparator:
    a: Any
    b: Any
    _: dataclasses.KEYWORD_ONLY
    key: Optional[Callable[whatever]] = None
```

The names `a` and `b` seem arbitrary, but they're not used only in the constructor, they're also available as attributes of the instances of Comparator, and dictionary keys in the `asdict()` return. Even if they were positional-only arguments to the constructor, that would forbid calling

comp = Comparator(a=1, b=2, key=operator.lt)

but it would still be possible to call

comp = Comparator(1, 2, key=operator.lt)
print(comp.a, comp.b)

Preventing them from being passed by name to the constructor seems to be adding an inconsistency, not removing one.

Perhaps it makes sense to be able to make init-only variables be positional-only, since they don't become part of the class's public API, but in that case it seems it could just be a flag passed to `InitVar`. Outside of init-only variables, positional-only arguments seem like a misfeature to me.

~Matt