How to add a dataclass field without annotating the type?

Question:

When there is a field in a dataclass for which the type can be anything, how can you omit the annotation?

@dataclass
class Favs:
    fav_number: int = 80085
    fav_duck = object()
    fav_word: str = 'potato'

It seems the code above doesn’t actually create a field for fav_duck. It just makes that a plain old class attribute.

>>> Favs()
Favs(fav_number=80085, fav_word='potato')
>>> print(*Favs.__dataclass_fields__)
fav_number fav_word
>>> Favs.fav_duck
<object at 0x7fffea519850>
Asked By: wim

||

Answers:

According to PEP 557 which defines the meaning of data classes,

The dataclass decorator examines the class to find fields. A field is defined as any variable identified in __annotations__. That is, a variable that has a type annotation.

Which is to say that the premise of this question (e.g. “How can I use dataclass with a field that has no type annotation) must be rejected. The term ‘field’ in the context of dataclass necessitates that the attribute has a type annotation by definition.

Note that using a generic type annotation like typing.Any is not the same as having an unannotated attribute, since the attribute will appear in __annotations__.

Finally, the helper function make_dataclass will automatically use typing.Any for the type annotation in cases when only an attribute name is supplied, and this is also mentioned in the PEP with an example.

Answered By: ely

The dataclass decorator examines the class to find fields, by looking for names in __annotations__. It is the presence of annotation which makes the field, so, you do need an annotation.

You can, however, use a generic one:

@dataclass
class Favs:
    fav_number: int = 80085
    fav_duck: 'typing.Any' = object()
    fav_word: str = 'potato'
Answered By: wim

Types Hints are an optional feature of Python. This also means, that using @dataclass does not require from you to define types.

In the annotation you can write many things. These are not checked if you don’t want them to be checked. These examples work:

@dataclass
class ColoredObject:
    color : "" 
    name : ""
@dataclass
class ColoredObject:
    color : ... 
    name : ...
@dataclass
class ColoredObject:
    color : True 
    name : True
@dataclass
class ColoredObject:
    color : object
    name : object
@dataclass
class ColoredObject:
    color : None
    name : None

I listed so many options here so that you can decide if you like some of them or not. It is your decision how you use code annotations.

For people who are used to languages that are more statically typed than Python, this may be an awkward style. It looks like abusing an empty string or the Ellipse object for this purpose. And that is true. But keep in mind that code readability is also important in programming. Interestingly most readers of your code would intuitively understand if you write ... without even knowing that there exists something like an Ellipse object.

Of course if you don’t want to confuse people who prefer type hints or if you want to use tools that expect correct type hints, you must establish an agreement on this.

The solution with typing.Any I have not listed. Of course that is a good solution. But types are an optional feature of Python. And that also means that knowing that there is something like typing.Any is optional knowledge.

Answered By: habrewning

If you are able to add a from __future__ import annotations at the top of the file, this will convert all annotations in the file to strings, so that they are then lazy-evaluated; this could be quite useful for defining annotations that don’t necessary need to be resolved at runtime.

For example, one way to use a short and simple type annotation (_) for a field:

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING


# added to silence any IDE warnings (i.e. PyCharm)
if TYPE_CHECKING:
    _ = object


@dataclass
class Favs:
    fav_number: int = 80085
    fav_duck: _ = object()
    fav_word: str = 'potato'


print(Favs())

Prints:

Favs(fav_number=80085, fav_duck=<object object at 0x11b754ad0>, fav_word='potato')

If you don’t want to or are unable to use a __future__ import (i.e. if you are on Python 3.6 or below, or want to silence IDE warnings of "unresolved references") you could always define a value for the type annotation beforehand:

from dataclasses import dataclass


# or: 
#   = ...
_ = object


@dataclass
class Favs:
    fav_number: int = 80085
    fav_duck: _ = object()
    fav_word: str = 'potato'


print(Favs())
Answered By: rv.kvetch