You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add a Dataclass abstract base class for instance checking and type annotations #102699
supports instance and subclass checks using similar logic as dataclasses.is_dataclass, and
blocks inheritance in __init_subclass__.
(Edit: removed "has an attribute __dataclass_fields__ for the purpose of type checking")
Pitch
This class would simplify runtime type-checking of dataclasses:
notisinstance(x, type) anddataclasses.is_dataclass(x) # Previouslyisinstance(x, dataclasses.Dataclass) # With this feature.
and similarly
isinstance(x, type) anddataclasses.is_dataclass(x) # Previously.issubclass(x, dataclasses.Dataclass) # With this feature.
Besides being simpler, it would also allow combined instance checks like isinstance(x, Dataclass | SomeOtherBase), which is arguably more Pythonic than the combined function calls above (marked "Previously").
It would also allow users to annotate dataclasses using Dataclass or type[Dataclass], which is currently impossible without reaching into protected areas of the typeshed.
Inheritance could be blocked for now with a friendly error message (that perhaps tells you to use the decorator either directly or in a base class's __init_subclass__).
changed the title [-]Add a Dataclass base class for instance checking and type annotations[/-][+]Add a Dataclass abstract base class for instance checking and type annotations[/+]on Mar 14, 2023
Thanks for opening this issue. I'm supportive of this idea -- I agree it would be quite helpful for typing.
To give context for those who don't follow stuff that goes on at typeshed/typing_extensions: we recently changed how we annotate some dataclasses-module functions over at typeshed. We now define a DataclassInstance protocol, which we use for our annotations for dataclasses.asdict, dataclasses.astuple, and dataclasses.replace. These are much more precise than our previous annotations, but the feedback we've received from users is that it's quite painful for users of type checkers not to be able to access our DataclassInstance protocol at runtime -- it's currently "private to typeshed", so can't easily be used in user code.
I think dataclasses.Dataclass would be a bad name for this class. dataclasses works through decorators, and I don't think anybody wants to change that, but the name dataclasses.Dataclass implies (to me) a concrete class that can be inherited from, similar to pydantic.BaseModel. To reflect that this is an abstract class, that users shouldn't even inherit from (since dataclasses works via decorators, not inheritance), maybe dataclasses.DataclassLike would be a better name? This would be a similar name to os.PathLike, which has a similar role. It would also make it slightly clearer that objects created by "dataclass-esque" third-party libraries (perhaps ones that use the dataclass_transform mechanism) could also be considered "instances of" this abstract base class if they have the __dataclass_fields__ attributes.
the name dataclasses.Dataclass implies a concrete class that can be inherited from, similar to pydantic.BaseModel.
I see your point about DataclassLike, but just to provide some reasoning for the simpler name: it's reflective of all the other ABCs like those in numbers and collections.abc. We don't have RealLike, but just Real; we don't have SequenceLike, but just Sequence.
And the function that currently checks dataclasses is called is_dataclass--not is_dataclass_like. So, from a natural language standpoint, I thought that since we say that that a class Xis a dataclass, it seemed natural to me to spell that isinstance(X, Dataclass).
I see your point about DataclassLike, but just to provide some reasoning for the simpler name: it's reflective of all the other ABCs like those in numbers and collections.abc. We don't have RealLike, but just Real; we don't have SequenceLike, but just Sequence.
Sure, but numbers and collections.abc are modules which are nothing but abstract base classes -- it would be surprising if anything from those modules wasn't an abstract base class. Whether it's put in typing or dataclasses, however, that won't hold true for this proposed class.
I don't have an issue with this. I think to some extent the dataclass design originally pushed in the direction of "this is just a utility to create ordinary classes, nobody should care that they were created by this particular utility." But in practice when you have a utility to create classes with a structured set of field metadata, people are going to find useful things to do with that field metadata, and in order to do that, they'll need to know which types have it. And this ship has long sailed, with the existence of free functions like dataclasses.fields, dataclasses.astuple, dataclasses.asdict that can only operate on instances of dataclasses, and the existence of dataclasses.is_dataclass to detect them.
So given that we already clearly support all of this, I don't see any reason we shouldn't make it a bit more ergonomic (and static type-checking friendly.)
I think to some extent the dataclass design originally pushed in the direction of "this is just a utility to create ordinary classes, nobody should care that they were created by this particular utility."
If you're interested in the history of this, from my discussion with the attrs creator, he was strongly opposed to inheritance in Python. Like you say, that ship has long sailed now 😄
I agree with Hynek about inheritance :) IMO this proposal is mostly orthogonal to inheritance; it's about ergonomically detecting whether a type has dataclass-style field metadata, which is relevant regardless of whether anyone is inheriting anything.
IMO this proposal is mostly orthogonal to inheritance; it's about ergonomically detecting whether a type has dataclass-style field metadata, which is relevant regardless of whether anyone is inheriting anything.
Yup, I was just providing some interesting history 😄 Glad to see there's so much agreement about having a base class.
I think that adding such an interface would make concrete assumptions about the internals of a dataclass. Right now __dataclass_fields__ is not even mentioned in https://docs.python.org/3/library/dataclasses.html
As far as I understand, dataclass module tries to hide this API: this is why we have dataclass.fields function and dataclass.is_dataclass (they are abstract enough to not expose any API).
Typeshed's stub for the class will have to have the __dataclass_fields__ attribute in order for type checkers to understand it properly, and I think it makes sense for the runtime to have that attribute as well. But I don't think that means that we need to document that it has that attribute or expose it as a public attribute in any way. I don't think this proposal would change the nature of this attribute for dataclasses at all, in fact -- it would still be an implementation detail. Users will only care that isinstance(<dataclass_instance>, DataclassLike) returns True; they won't care that it returns True because the isinstance machinery is checking to see if <dataclass_instance> has a __dataclass_fields__ attribute.
I don't really see the point of reserving the Dataclass name. Calling it DataclassLike rather makes me wonder what, besides @dataclass decorated classes, would be caught by isinstance(cls, DataclassLike).
Also, why use an abstract base class here, instead of a Protocol? It seems that in the current PR it doesn't have any @abstractmethod.
I don't really see the point of reserving the Dataclass name. Calling it DataclassLike rather makes me wonder what, besides @dataclass decorated classes, would be caught by isinstance(cls, DataclassLike).
Other classes from third-party libraries that have dataclass-like semantics, and also synthesize __dataclass_fields__ attributes. Many of these third-party libraries make use of the @dataclass_transform decorator, so they are treated by type checkers as though they have the same semantics as dataclasses.
I think a dataclasses.Dataclass class also sounds like you could inherit from it to create a dataclass (similar to enum.Enum or typing.Protocol), and that's not the intent of the new ABC. Dataclasses works via decorators, and we don't want to change that.
Also, why use an abstract base class here, instead of a Protocol?
What would using a protocol get us here? The runtime implementation for typing.Protocol is much more complicated and often has unexpected performance issues. dataclasses currently takes pains to avoid importing typing (so that it has a reasonably fast import time, and doesn't need to depend on typing), and I don't see a reason to use typing.Protocol here, when we can do everything we need with ABCs.
For os.PathLike and contextlib.AbstractContextManager, they're implemented as ABCs (so that they're fast) at runtime, but in typeshed, we pretend they're protocols, and type checkers treat them as though they're protocols. It works great; I imagine we'd do a very similar thing with this new ABC.
It seems that in the current PR it doesn't have any @abstractmethod.
Correct, but we need the ABCMeta metaclass in order for the custom __subclasshook__ method to work as designed.
Using a protocol and allowing subclassing would allow composition with other protocols, which in my opinion would be useful. It allows creating a function that takes as argument a value that both can be passed to asdict, and has some special attribute or method.
(Keeping in mind that Python doesn't have intersections, and that the only way to compose protocols is by using subclassing).
Activity
[-]Add a Dataclass base class for instance checking and type annotations[/-][+]Add a Dataclass abstract base class for instance checking and type annotations[/+]AlexWaygood commentedon Mar 14, 2023
Thanks for opening this issue. I'm supportive of this idea -- I agree it would be quite helpful for typing.
To give context for those who don't follow stuff that goes on at typeshed/typing_extensions: we recently changed how we annotate some
dataclasses-module functions over at typeshed. We now define aDataclassInstanceprotocol, which we use for our annotations fordataclasses.asdict,dataclasses.astuple, anddataclasses.replace. These are much more precise than our previous annotations, but the feedback we've received from users is that it's quite painful for users of type checkers not to be able to access ourDataclassInstanceprotocol at runtime -- it's currently "private to typeshed", so can't easily be used in user code.I think
dataclasses.Dataclasswould be a bad name for this class. dataclasses works through decorators, and I don't think anybody wants to change that, but the namedataclasses.Dataclassimplies (to me) a concrete class that can be inherited from, similar topydantic.BaseModel. To reflect that this is an abstract class, that users shouldn't even inherit from (since dataclasses works via decorators, not inheritance), maybedataclasses.DataclassLikewould be a better name? This would be a similar name toos.PathLike, which has a similar role. It would also make it slightly clearer that objects created by "dataclass-esque" third-party libraries (perhaps ones that use thedataclass_transformmechanism) could also be considered "instances of" this abstract base class if they have the__dataclass_fields__attributes.Cc. @ericvsmith and @carljm for dataclasses.
NeilGirdhar commentedon Mar 14, 2023
I see your point about
DataclassLike, but just to provide some reasoning for the simpler name: it's reflective of all the other ABCs like those innumbersandcollections.abc. We don't haveRealLike, but justReal; we don't haveSequenceLike, but justSequence.And the function that currently checks dataclasses is called
is_dataclass--notis_dataclass_like. So, from a natural language standpoint, I thought that since we say that that a classXis a dataclass, it seemed natural to me to spell thatisinstance(X, Dataclass).But
DataclassLikewould be fine too.AlexWaygood commentedon Mar 14, 2023
Sure, but
numbersandcollections.abcare modules which are nothing but abstract base classes -- it would be surprising if anything from those modules wasn't an abstract base class. Whether it's put intypingordataclasses, however, that won't hold true for this proposed class.carljm commentedon Mar 14, 2023
I don't have an issue with this. I think to some extent the dataclass design originally pushed in the direction of "this is just a utility to create ordinary classes, nobody should care that they were created by this particular utility." But in practice when you have a utility to create classes with a structured set of field metadata, people are going to find useful things to do with that field metadata, and in order to do that, they'll need to know which types have it. And this ship has long sailed, with the existence of free functions like
dataclasses.fields,dataclasses.astuple,dataclasses.asdictthat can only operate on instances of dataclasses, and the existence ofdataclasses.is_dataclassto detect them.So given that we already clearly support all of this, I don't see any reason we shouldn't make it a bit more ergonomic (and static type-checking friendly.)
Would like to know what @ericvsmith thinks.
NeilGirdhar commentedon Mar 14, 2023
If you're interested in the history of this, from my discussion with the attrs creator, he was strongly opposed to inheritance in Python. Like you say, that ship has long sailed now 😄
carljm commentedon Mar 14, 2023
I agree with Hynek about inheritance :) IMO this proposal is mostly orthogonal to inheritance; it's about ergonomically detecting whether a type has dataclass-style field metadata, which is relevant regardless of whether anyone is inheriting anything.
NeilGirdhar commentedon Mar 14, 2023
Yup, I was just providing some interesting history 😄 Glad to see there's so much agreement about having a base class.
sobolevn commentedon Mar 15, 2023
I think that adding such an interface would make concrete assumptions about the internals of a dataclass. Right now
__dataclass_fields__is not even mentioned in https://docs.python.org/3/library/dataclasses.htmlAs far as I understand,
dataclassmodule tries to hide this API: this is why we havedataclass.fieldsfunction anddataclass.is_dataclass(they are abstract enough to not expose any API).NeilGirdhar commentedon Mar 15, 2023
Maybe we could just remove that from the proposal? In retrospect it was a mistake to add it.
AlexWaygood commentedon Mar 15, 2023
Typeshed's stub for the class will have to have the
__dataclass_fields__attribute in order for type checkers to understand it properly, and I think it makes sense for the runtime to have that attribute as well. But I don't think that means that we need to document that it has that attribute or expose it as a public attribute in any way. I don't think this proposal would change the nature of this attribute for dataclasses at all, in fact -- it would still be an implementation detail. Users will only care thatisinstance(<dataclass_instance>, DataclassLike)returnsTrue; they won't care that it returnsTruebecause theisinstancemachinery is checking to see if<dataclass_instance>has a__dataclass_fields__attribute.pythongh-102699: Add `dataclasses.DataclassLike`
pythongh-102699: Add `dataclasses.DataclassLike`
AlexWaygood commentedon Mar 22, 2023
Okay, to put some flesh on the bones of this proposal: here's a PR showing what this could look like:
dataclasses.DataclassLike#102933randolf-scholz commentedon May 10, 2023
I don't really see the point of reserving the
Dataclassname. Calling itDataclassLikerather makes me wonder what, besides@dataclassdecorated classes, would be caught byisinstance(cls, DataclassLike).Also, why use an abstract base class here, instead of a
Protocol? It seems that in the current PR it doesn't have any@abstractmethod.AlexWaygood commentedon May 12, 2023
Other classes from third-party libraries that have dataclass-like semantics, and also synthesize
__dataclass_fields__attributes. Many of these third-party libraries make use of the@dataclass_transformdecorator, so they are treated by type checkers as though they have the same semantics as dataclasses.As a concrete example:
I think a
dataclasses.Dataclassclass also sounds like you could inherit from it to create a dataclass (similar toenum.Enumortyping.Protocol), and that's not the intent of the new ABC. Dataclasses works via decorators, and we don't want to change that.What would using a protocol get us here? The runtime implementation for
typing.Protocolis much more complicated and often has unexpected performance issues.dataclassescurrently takes pains to avoid importingtyping(so that it has a reasonably fast import time, and doesn't need to depend ontyping), and I don't see a reason to usetyping.Protocolhere, when we can do everything we need with ABCs.For
os.PathLikeandcontextlib.AbstractContextManager, they're implemented as ABCs (so that they're fast) at runtime, but in typeshed, we pretend they're protocols, and type checkers treat them as though they're protocols. It works great; I imagine we'd do a very similar thing with this new ABC.Correct, but we need the
ABCMetametaclass in order for the custom__subclasshook__method to work as designed.antonagestam commentedon Oct 3, 2023
Using a protocol and allowing subclassing would allow composition with other protocols, which in my opinion would be useful. It allows creating a function that takes as argument a value that both can be passed to asdict, and has some special attribute or method.
(Keeping in mind that Python doesn't have intersections, and that the only way to compose protocols is by using subclassing).
antonagestam commentedon Oct 3, 2023
Would DataclassType make sense for name?
NeilGirdhar commentedon May 18, 2026
This is in the typeshed now.