So, I’ve done some thinking on this and have some modifications to my original proposal:
-
Add a
require_decorator[1] parameter tois_dataclass(default as False), this will check that__dataclass_fields__is in the class dict and not just inherited. -
Make
_is_dataclass_instancepublic and addis_dataclass_type.- I think these are just useful helpers, I don’t see why
_is_dataclass_instanceneeds to be private, I’ve seen it duplicated enough and it’s not something we’re likely to change.
- I think these are just useful helpers, I don’t see why
-
Add a
__generated_for_dataclass__attribute to dataclass methods that is a reference to the class the method was actually created for, potentially along with a functionget_method_class[2] to retrieve this that returnsNoneif the attribute doesn’t exist. -
Add an
is_frozenfunction to check if a class/instance is actually itself frozen, meaning you can’t set arbitrary attributes.- Essentially shorthand for checking the class
__setattr__and__delattr__were generated for the exact class being examined. - Potentially we could also have
has_frozen_fieldsto indicate the presence of some inherited frozen fields, but I’m not sure this is that useful and it can get pretty messy if you start to consider multiple inheritance cases (like frozen ‘fields’ that aren’t in fields(cls)).
- Essentially shorthand for checking the class
-
Don’t provide
paramsto get the parameters- I’ve changed my mind here after changing the attribute attached to the method to give the class the method was generated for which covers the original use case.
- Many of the parameters will be ignored[3] depending on other class features, so the features should be checked directly instead of relying on parameters that may not reflect the state of the actual class.
With these changes:
- Checking or replacing methods
To check if a __repr__ has been generated for a specific class you can use:
get_method_class(t.__repr__) is t
To check if you can replace a __repr__ you’d now use something like:
if (repr_cls := get_method_class(cls.__repr__))): ...
As repr_cls (if it is not None) is the class used to generate the original __repr__ that would otherwise be used, dataclasses.fields(repr_cls) can be used to construct a replacement without needing any other requirements.
The behaviour of enum __repr__ replacement would change slightly so that if __repr__ has been replaced it would no longer make a custom enum repr, rather than doing so based solely on whether repr=True is declared in the dataclass params. The dataclasses documentation states that if the __repr__ is defined, the repr parameter should be ignored and the enum behaviour should really respect this.
- Distinguishing decorated and undecorated classes
>>> from dataclasses import _FIELDS, dataclass
>>> def is_dataclass(obj, *, require_decorator=False):
... cls = obj if isinstance(obj, type) else type(obj)
... if require_decorator:
... return _FIELDS in cls.__dict__
... return hasattr(cls, _FIELDS)
...
>>> @dataclass
... class A:
... a: int = 1
...
>>> @dataclass
... class B:
... b: int = 2
...
>>> @dataclass
... class C(B, A):
... pass
...
>>> class D(B, A):
... pass
...
>>> is_dataclass(C), is_dataclass(D)
(True, True)
>>> is_dataclass(C, require_decorator=True), is_dataclass(D, require_decorator=True)
(True, False)