16

I have the following class:

class WordItem:
    def __init__(self, phrase: str, word_type: WORD_TYPE):
        self.id = f'{phrase}_{word_type.name.lower()}'
        self.phrase = phrase
        self.word_type = word_type

    @classmethod
    def from_payload(cls, payload: Dict[str, Any]) -> 'WordItem':
        return cls(**payload)

How can I rewrite this class as a dataclass?

Specifically, how should the id field be declared? It has a generated value, and is not a field that the code creating instances would provide.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
indigo153
  • 1,021
  • 2
  • 10
  • 23

1 Answers1

33

Just move each of your attributes to a type-annotated declaration on the class, where the class has been decorated with the @dataclasses.dataclass decorator.

You can generate the value for id in a __post_init__ method; make sure you mark it as exempt from the __init__ arguments with a dataclass.field() object:

from dataclasses import dataclass, field    

@dataclass
class WordItem:
    id: str = field(init=False)
    phrase: str
    word_type: WORD_TYPE

    def __post_init__(self):
        self.id = f'{self.phrase}_{self.word_type.name.lower()}'

Demo:

>>> from types import SimpleNamespace
>>> wt = SimpleNamespace(name='Python')
>>> WordItem('await', wt)
WordItem(id='await_python', phrase='await', word_type=namespace(name='Python'))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • what if my prev implementation had the following method: @classmethod def from_payload(cls, payload: Dict[str, Any]) -> 'WordItem': return cls(**payload) how can I use it? – indigo153 Oct 03 '18 at 13:35
  • 2
    @petrush: please don't move the goal posts. Class methods work no different on dataclasses; the example in my question essentially generates the same `__init__` method as before, you can add class methods to that class just the same. – Martijn Pieters Oct 03 '18 at 13:36
  • So your classmethod would work exactly as written; you can put `from_payload` on the dataclass without any changes and it'll continue to work. – Martijn Pieters Oct 03 '18 at 13:37
  • 4
    To generate IDs you can also use default fields. E.g. `id: str = field(default_factory=lambda: str(uuid.uuid4()))` – JVillella Jun 27 '20 at 23:33
  • 1
    @JVillella: sure, but that's not what is being asked here. You can't access other field values there. – Martijn Pieters Jun 30 '20 at 21:17