type hint for an instance of a non specific dataclass

Despite its name, dataclasses.dataclass doesn’t expose a class interface. It just allows you to declare a custom class in a convenient way that makes it obvious that it is going to be used as a data container. So, in theory, there is little opportunity to write something that only works on dataclasses, because dataclasses really are just ordinary classes.

In practice, there a couple of reasons why you would want to declare dataclass-only functions anyway, and something like this is how you should go about it:

from dataclasses import dataclass
from typing import ClassVar, Dict, Protocol


class IsDataclass(Protocol):
    # as already noted in comments, checking for this attribute is currently
    # the most reliable way to ascertain that something is a dataclass
    __dataclass_fields__: ClassVar[Dict] 

def dataclass_only(x: IsDataclass):
    ...  # do something that only makes sense with a dataclass

@dataclass
class Foo:
    pass

class Bar:
    pass

dataclass_only(Foo())  # a static type check should show that this line is fine ..
dataclass_only(Bar())  # .. and this one is not

This approach is also what you alluded to in your question. If you want to go for it, keep in mind that you’ll need a third party library such as mypy to do the static type checking for you, and if you are on python 3.7 or earlier, you need to manually install typing_extensions since Protocol only became part of the standard library in 3.8.

Also noted that older version of mypy (>=0.982) mistakenly expect __dataclass_fields__ to be an instance attribute, so the protocol should be just __dataclass_fields__: Dict[1].


When I first wrote it, this post also featured The Old Way of Doing Things, back when we had to make do without type checkers. I’m leaving it up, but it’s not recommended to handle this kind of feature with runtime-only failures any more:

from dataclasses import is_dataclass

def dataclass_only(x):
    """Do something that only makes sense with a dataclass.
    
    Raises:
        ValueError if something that is not a dataclass is passed.
        
    ... more documentation ...
    """
    if not is_dataclass(x):
        raise ValueError(f"'{x.__class__.__name__}' is not a dataclass!")
    ...

[1]Kudos to @Kound for updating and testing the ClassVar behavior.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)