Check if a class is a dataclass in Python
Asked Answered
D

3

39

How can I check if a class is a dataclass in Python?

I found out that I can check the existance of __dataclass_fields__ and __dataclass_params__ attributes, but I'd love to find a more elegant way to do this.

I'd expect using something like inspect.isclass function. Maybe something like inspect.isdataclass, for example.

Does Python have something like that?

Thanks.

Demulsify answered 13/5, 2019 at 5:17 Comment(0)
S
61

Docs

import dataclasses
dataclasses.is_dataclass(something)

As mentioned by @Arne internally it simply checks hasattr(something, '__dataclass_fields__'), but I'd recommend to not rely on this attribute and directly use is_dataclass.

Why you should not rely on __dataclass_fields__:

  • This attribute is not a public API: it's not mentioned anywhere in the docs.
  • It's an implementation detail, and so it's not guaranteed to work in other python implementations. But besides cPython nothing seems to support Python3.7 yet (as of May 2019). At least Jython, IronPython and PyPy do not support it, so it's hard to tell if they will be using the same attribute or not

Everything else including differences between checking for dataclass type and dataclass instance is in the docs of is_dataclass method:

# (from the docs)
def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)
Swinge answered 13/5, 2019 at 5:29 Comment(3)
Note that all is_dataclass does under the hood is checking for __dataclass_fields__.Whelan
It has been exposed in python 3.83 API docs.python.org/3/library/…Trenttrento
This does not complete work! A subclass from a dataclass will also pass return True on is_dataclass() -- as in inherits the dataclass_* fields ... It is semantic issue whether such a subclass is a dataclass or not ...Hazardous
L
6

Extending upon the answer above, the following illustrates the usage of is_dataclass():

Remember: The parameter passed to is_dataclass() can be a dataclass or an instance of the dataclass, to return True from the method call.

In [1]: from dataclasses import dataclass

In [2]: @dataclass
   ...: class Bio:
   ...:     name: str
   ...:     age: int
   ...:     height: float
   ...:

In [3]: from dataclasses import is_dataclass

In [4]: is_dataclass(Bio)
Out[4]: True

In [5]: b = Bio('John', 25, 6.5)

In [6]: is_dataclass(b)
Out[6]: True

To check whether, b is an instance of the dataclass and not a dataclass itself:

In [7]: is_dataclass(b) and not isinstance(b, type)
Out[7]: True

Bio is a dataclass, so the following expression evaluates to False:

In [8]: is_dataclass(Bio) and not isinstance(Bio, type)
Out[8]: False

Lets check for a regular class:

In [9]: class Car:
   ...:     def __init__(self, name, color):
   ...:         self.name = name
   ...:         self.color = color
   ...:

We know Car is not a dataclass:

In [10]: is_dataclass(Car)
Out[10]: False

In [11]: c = Car('Mustang', 'Blue')

Neither an instance of Car is a dataclass instance:

In [12]: is_dataclass(c)
Out[12]: False
Lafrance answered 13/5, 2019 at 6:37 Comment(2)
Thanks! I'd prefer checking if it's a class or an instance using inspect.isclass over isinstance(b, type), but it actually does the same.Demulsify
Do you want to add a check for a non-dataclass that has an attribute called __dataclass_fields__? =DWhelan
H
0

To verify whether a class is defined with @dataclass, testing for the __dataclass_* attributes (with dataclass.isdataclass()) will not do. As a (normal) subclass will inherit those field(s) ...

This can be an issue with mutation-testing (see for example "mutmut"). It will report deleting @dataclass is not seen in a test. That is annoying (mostly when subclassing, without adding new annotations)

As a workaround: we can check for an own __init__ – as @dataclass will typically generate one.

def isDataClass(cls):
    assert dataclasses.is_dataclass(cls) # This will also pass when cls inherits from a dataclass
    my_init = getattr(cls, '__init__')
    inherited_init = getattr(cls.mro()[1], '__init__')
    assert my_init is not inherited_init, f"Probably you subclasses a dataclass, but forgot @dataclass for {cls}"

Not perfect, but it does help.

Hazardous answered 10/4 at 7:13 Comment(3)
why not check for the __dataclass_* attributes in the same way? Why __init__? It seems much more brittle. And also, another suggestion, maybe just inspect the cls.__dict__ direcly, so the above could just be something like assert "__init__" in vars(cls) (or the equivalent, asssert "__init__" in cls.__dict__Elephant
The @dataclass decorator generates a (new) __init__ method. By testing that it differs from the inherited one, we know cls didn't (only) inherited from a data(base)class. Sure, it not 100% safe. But at least we can test the developer didn't forget @dataclass for a sub-class. One of the things MutMut (mutation testing) will check. I prefer dataclasses.is_dataclass() as that is public, whereas __dataclass_* is an implementation detail. Although that (api) functions does exactly that.Hazardous
Yes, I am aware, but I think it would be more reliable than checking for __init__.Elephant

© 2022 - 2024 — McMap. All rights reserved.