Set the class __str__ as a property in the dataclass decorator

My idea: Add a name parameter to @dataclass, to override __str__ on the class.

Something like:

from dataclasses import dataclass

@dataclass(name="An Address")
class Address:
    ...

# An address

Here’s my workaround based on this :

from dataclasses import dataclass

class str_meta(type):
    def __str__(cls):
        return getattr(cls, 'meta_str', cls.__name__)

@dataclass
class Address(metaclass=str_meta):
    meta_str = "An address"
    ...


print(Address
# An address

Improvements to my workaround and / or feedback welcome.

I’m not sure why you need this. You can just define your own __str__ if you want to customise it:

>>> @dataclass
... class A:
...     x: int
...     def __str__(self): return "This is class A"
...
>>> a = A(1)
>>> str(a)
'This is class A'

You can also define your own __repr__ if you want to customise that.

2 Likes

Your goal is to change the way the class is displayed, not it’s instances? Why?

1 Like

I’m talking about __str__ for the class:

>>> str(A)
<class '__main__.A'>

Because I’m programmatically creating forms based on dataclasses, and in some cases I want to have a different class name to the name displayed on the form.

… although now you mention it maybe I should just treat it as a localization problem.

1 Like

Why are you characterizing this as a workaround instead of a solution?

2 Likes
from dataclasses import dataclass


class MetaStr(type):
    def __new__(mcls, name, bases, namespace, meta_str=None, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        cls.meta_str = meta_str or name
        return cls

    def __str__(cls):
        return getattr(cls, 'meta_str', cls.__name__)


@dataclass
class Address(metaclass=MetaStr, meta_str="An address"):
    street: str = ""
    city: str = ""


print(Address)  # An address
1 Like

I think this is a bit too niche for the standard library. Even if dataclass() supported something like str="...", I would expect it to affect the instance’s __str__, not the class. Personally, I’d just use a ClassVar here:

from dataclasses import dataclass
from typing import ClassVar

@dataclass
class Address:
    label: ClassVar[str] = "An Address"
    x: int
    y: int

print(Address.label)  # An Address
Address(1, 2)         # Address(x=1, y=2)

It’s a bit more typing to access .label, but it feels cleaner than using a metaclass, which seems like overkill.

6 Likes

Because @dataclass(name="An Address") isn’t in the the standard library yet.

And it will never be with the semantics you are suggesting. I am saying this with a 99% certainty. It’s too niche of a feature, to weird of a behavior. No other class in the stdlib has anything close to this (except the typing “classes”, but those are a very different edge case). In third party libraries I can’t think of any examples either. If I saw something like this in code I had to review, I would reject it. Use a ClassVar, as already suggested.

2 Likes

Or, simply a class variable:

class Address:
    x: int
    y: int
    label = "An Address"
1 Like

…what do you think ClassVar signifies? :grinning_face_with_smiling_eyes:

This is not relevant to the issue at hand. Creating class variables does not require annotations, and this is not a question about typing. It’s like saying ‘annotate the variable as an int’ instead of simply ‘use an integer.’

Do lambdas as arguments for dataclass attributes work? If so, doing


from dataclass import dataclass

@dataclass
class Address:
    __str__: Callable[[type[Address]], str] = lambda cls: "An Address"

could work, although I think the __str__ would probably rather be invoked for instances. I’d still give it a try.

If the str(Address) part is only needed for internal code, some attribute like __internal_string__ could also easily be used, in conjunction with getattr(obj, "__internal_string__", str(obj)) if you don’t know wether the object is a Address (or other similar dataclasses).

1 Like