This article from Glyph talks about not using the __init__ dunder function to initialize a class and instead use the dataclass in the standard library. I read his post and experimented with this new way to make a constructor and decided that I totally agreed – this was a much cleaner way to initialize a class. I will let you read his arguments about why this is so much better.

But I hit a gotcha as I was implementing a new class, and it was more to do with my understanding of how Dataclasses work than actually using it in an constructor. Here is an example:

from dataclasses import dataclass
from initializers import some_expensive_client

@dataclass
class MyService:
    client = some_expensive_client()

    ## more methods

some_expensive_client is just a wrapper around an outside API. In my case, it was an AWS/boto3 client. I'm not sure what type it was to make Python's typing system happy and, to be frank, I don't really care that much (yes, I'm a bit old-fashioned in my Python ways). But that was a mistake.

When I was writing a test, I wanted to client via MyService(client=mock_client). But then I got an AWS error that client wasn't valid. I scratched my head … my mock_client didn't have anything to do with AWS. Then noticed that I didn't specify a type, which a dataclass expects. So I just added Any.

from dataclasses import dataclass
from typing import Any
from initializers import some_expensive_client

@dataclass
class MyService:
    client : Any = some_expensive_client()

    ## more methods

And then I could do MyService(client=mock_client) with no problem. So what seems to happen is if you don't specify a type, the @dataclass gets confused and doesn't actually initialize it (or treat it like a normal property). Once you add the type (even a generic Any) the magic happens.