Created
January 29, 2020 17:30
-
-
Save noamkush/30574dd552faeed2e460ea72d570b533 to your computer and use it in GitHub Desktop.
Dataclasses with keyword only arguments, allowing for inheritance with defaults (tested with 3.6 backport)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import dataclasses | |
def _init_fn(fields, frozen, has_post_init, self_name): | |
# fields contains both real fields and InitVar pseudo-fields. | |
globals = {'MISSING': dataclasses.MISSING, | |
'_HAS_DEFAULT_FACTORY': dataclasses._HAS_DEFAULT_FACTORY} | |
body_lines = [] | |
for f in fields: | |
line = dataclasses._field_init(f, frozen, globals, self_name) | |
# line is None means that this field doesn't require | |
# initialization (it's a pseudo-field). Just skip it. | |
if line: | |
body_lines.append(line) | |
# Does this class have a post-init function? | |
if has_post_init: | |
params_str = ','.join(f.name for f in fields | |
if f._field_type is dataclasses._FIELD_INITVAR) | |
body_lines.append(f'{self_name}.{dataclasses._POST_INIT_NAME}({params_str})') | |
# If no body lines, use 'pass'. | |
if not body_lines: | |
body_lines = ['pass'] | |
locals = {f'_type_{f.name}': f.type for f in fields} | |
return dataclasses._create_fn('__init__', | |
[self_name, '*'] + [dataclasses._init_param(f) for f in fields if f.init], | |
body_lines, | |
locals=locals, | |
globals=globals, | |
return_type=None) | |
def add_init(cls, frozen): | |
fields = getattr(cls, dataclasses._FIELDS) | |
# Does this class have a post-init function? | |
has_post_init = hasattr(cls, dataclasses._POST_INIT_NAME) | |
# Include InitVars and regular fields (so, not ClassVars). | |
flds = [f for f in fields.values() | |
if f._field_type in (dataclasses._FIELD, dataclasses._FIELD_INITVAR)] | |
dataclasses._set_new_attribute(cls, '__init__', | |
_init_fn(flds, | |
frozen, | |
has_post_init, | |
# The name to use for the "self" | |
# param in __init__. Use "self" | |
# if possible. | |
'__dataclass_self__' if 'self' in fields | |
else 'self', | |
)) | |
return cls | |
# a dataclass with a constructor that only takes keyword arguments | |
def dataclass_keyword_only(_cls=None, *, repr=True, eq=True, order=False, | |
unsafe_hash=False, frozen=False): | |
def wrap(cls): | |
cls = dataclasses.dataclass( | |
cls, init=False, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen) | |
return add_init(cls, frozen) | |
# See if we're being called as @dataclass or @dataclass(). | |
if _cls is None: | |
# We're called with parens. | |
return wrap | |
# We're called as @dataclass without parens. | |
return wrap(_cls) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment