title: Python dataclasses inheritance and default values
body: <p>Given the following hierarchy of python dataclasses:</p>
  <pre><code>@dataclass
  class A:
      a: str
      aa: str
  
  
  @dataclass
  class B(A):
      b: str
  
  
  @dataclass
  class C(A):
      c: str
  
  
  @dataclass
  class D(B, C):
      d: str
  </code></pre>
  <p>Is there any general method that allows you to insert an attribute with a default value in this inheritance hierarchy? For instance, the following produces a <code>Non-default argument(s) follows default argument(s) defined in 'B'</code> error for class <code>D</code>.</p>
  <pre><code>@dataclass
  class A:
      a: str
      aa: str
  
  
  @dataclass
  class B(A):
      b: str = 'b'  # &lt;-- default value
  
  
  @dataclass
  class C(A):
      c: str
  
  
  @dataclass
  class D(B, C):
      d: str
  </code></pre>
  <p>I suppose one solution could be to give all attributes in the hierarchy a default value of <code>None</code>, and add a <code>__post_init__()</code> validation which raises some exception if any attributes are <code>None</code>, but this doesn't seem right and you would need it in every dataclass in the hierarchy.</p>
  <p><a href="https://stackoverflow.com/questions/51575931/class-inheritance-in-python-3-7-dataclasses">Class inheritance in Python 3.7 dataclasses</a> proposes a solution where you create two separate branches of the inheritance hierarchy; one for default values and one without. The drawback here is that the hierarchy quickly becomes mangled with a lot of classes, but if there are no better options, I suppose this is as good as it gets.</p>
  <h1>Edit</h1>
  <p>I've come up with a <code>meta_dataclass</code> which does some hackery to at least solve my problem. I'm sure it breaks all kinds of rules, but perhaps someone can improve upon it.</p>
  <p>It allows a hierarchy of <code>meta_dataclasses</code> where any attribute in the hierarchy can have a default value. It is motivated by the solution presented in <a href="https://stackoverflow.com/questions/51575931/class-inheritance-in-python-3-7-dataclasses">Class inheritance in Python 3.7 dataclasses</a>. It effectively creates two inheritance branches, one with default values and one without, but the creation of the extra classes required to achieve this is encapsulated in the <code>mete_dataclass</code> decorator. A current drawback is that a <code>meta_dataclass</code> can only inherit from other <code>meta_dataclasses</code>.</p>
  <p><strong>Full code</strong>:</p>
  <pre><code>from dataclasses import make_dataclass, MISSING, field
  
  
  class Dataclass:
      pass
  
  
  class Declarations:
      pass
  
  
  class Definitions:
      pass
  
  
  def meta_dataclass(cls=None, /, *, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False):
      def wrap(cls):
          declaration_bases = []
          definition_bases = []
  
          for base in cls.__bases__:
              if issubclass(base, Dataclass):
                  declaration_bases += [c for c in base.__bases__ if issubclass(c, Declarations)]
                  definition_bases += [c for c in base.__bases__ if issubclass(c, Definitions)]
              elif len(cls.__bases__) == 1 and base != object:
                  raise ValueError(f'meta_dataclasses can only inherit from other meta_dataclasses. '
                                   f'{cls} inherits from {base}')
  
          declaration_bases.append(Declarations)
          definition_bases.append(Definitions)
  
          fields = []
          if hasattr(cls, '__annotations__'):
              for field_name, field_type in cls.__annotations__.items():
                  f = field(default=cls.__dict__[field_name]) if field_name in cls.__dict__ else field()
                  fields.append((field_name, field_type, f))
  
          declarations = make_dataclass(cls_name=f'{cls.__name__}_Declarations',
                                        bases=tuple(declaration_bases),
                                        fields=[f for f in fields if isinstance(f[2].default, type(MISSING))],
                                        init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen)
  
          definitions = make_dataclass(cls_name=f'{cls.__name__}_Definitions',
                                       bases=tuple(definition_bases),
                                       fields=[f for f in fields if not isinstance(f[2].default, type(MISSING))],
                                       init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen)
  
          cls_wrapper = make_dataclass(cls_name=cls.__name__, fields=[], bases=(Dataclass, definitions, declarations),
                                       namespace=cls.__dict__, init=init, repr=repr, eq=eq, order=order,
                                       unsafe_hash=unsafe_hash, frozen=frozen)
  
          return cls_wrapper
  
      if cls is None:
          return wrap
      else:
          return wrap(cls)
  
  </code></pre>
  <p><strong>Example</strong>:</p>
  <pre><code>@meta_dataclass
  class A:
      a: str
      aa: str
  
  @meta_dataclass
  class B(A):
      b: str = 'b'
  
  @meta_dataclass
  class C(A):
      c: str
  
  @meta_dataclass
  class D(B, C):
      d: str
  
  
  &gt;&gt;&gt; help(D)
  Help on class D in module types:
  
  class D(__main__.Dataclass, D_Definitions, D_Declarations)
   |  D(a: str, aa: str, c: str, d: str, b: str = 'b') -&gt; None
   |  
   |  D(a: str, aa: str, c: str, d: str, b: str = 'b')
   |  
   |  Method resolution order:
   |      D
   |      __main__.Dataclass
   |      D_Definitions
   |      B_Definitions
   |      C_Definitions
   |      A_Definitions
   |      __main__.Definitions
   |      D_Declarations
   |      B_Declarations
   |      C_Declarations
   |      A_Declarations
   |      __main__.Declarations
   |      builtins.object
  </code></pre>
  <p><strong>Illustration</strong>:</p>
  <p>The below image illustrates what the <code>@meta_dataclass</code> decorator does to a single class.</p>
  <p><a href="https://i.stack.imgur.com/8nuoC.png" rel="noreferrer"><img src="https://i.stack.imgur.com/8nuoC.png" alt="Illustration of what @meta_dataclass does" /></a></p>