diff options
author | da-woods <dw-git@d-woods.co.uk> | 2022-07-29 18:58:31 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-29 19:58:31 +0200 |
commit | 9b69292ee4f5340994776cac4ba18c27c9578ea1 (patch) | |
tree | 4c33700f40ca6ef9923513fac8a7c9abc6309417 | |
parent | 11e4b1f11c3849622007481d92c3589ce5cfbec8 (diff) | |
download | cython-9b69292ee4f5340994776cac4ba18c27c9578ea1.tar.gz |
Fix initialization of init=False dataclass fields (GH-4908)
init=False (in a field name) means that it isn't an argument to
`__init__`. It does not mean that the field isn't initialized if
a default method of initialization is provided.
-rw-r--r-- | Cython/Compiler/Dataclass.py | 10 | ||||
-rw-r--r-- | tests/run/pure_cdef_class_dataclass.py | 41 |
2 files changed, 47 insertions, 4 deletions
diff --git a/Cython/Compiler/Dataclass.py b/Cython/Compiler/Dataclass.py index 0d0bb4768..87ef5e76a 100644 --- a/Cython/Compiler/Dataclass.py +++ b/Cython/Compiler/Dataclass.py @@ -341,8 +341,6 @@ def generate_init_code(init, node, fields, kw_only): seen_default = False for name, field in fields.items(): - if not field.init.value: - continue entry = node.scope.lookup(name) if entry.annotation: annotation = u": %s" % entry.annotation.string.value @@ -357,18 +355,22 @@ def generate_init_code(init, node, fields, kw_only): ph_name = get_placeholder_name() placeholders[ph_name] = field.default # should be a node assignment = u" = %s" % ph_name - elif seen_default and not kw_only: + elif seen_default and not kw_only and field.init.value: error(entry.pos, ("non-default argument '%s' follows default argument " "in dataclass __init__") % name) return "", {}, [] - args.append(u"%s%s%s" % (name, annotation, assignment)) + if field.init.value: + args.append(u"%s%s%s" % (name, annotation, assignment)) if field.is_initvar: continue elif field.default_factory is MISSING: if field.init.value: function_body_code_lines.append(u" %s.%s = %s" % (selfname, name, name)) + elif assignment: + # not an argument to the function, but is still initialized + function_body_code_lines.append(u" %s.%s%s" % (selfname, name, assignment)) else: ph_name = get_placeholder_name() placeholders[ph_name] = field.default_factory diff --git a/tests/run/pure_cdef_class_dataclass.py b/tests/run/pure_cdef_class_dataclass.py index 8a978d36f..b3892586f 100644 --- a/tests/run/pure_cdef_class_dataclass.py +++ b/tests/run/pure_cdef_class_dataclass.py @@ -29,3 +29,44 @@ class MyDataclass: a: int = 1 self: list = cython.dataclasses.field(default_factory=list, hash=False) # test that arguments of init don't conflict + + +class DummyObj: + def __repr__(self): + return "DummyObj()" + + +@cython.dataclasses.dataclass +@cython.cclass +class NoInitFields: + """ + >>> NoInitFields() + NoInitFields(has_default=DummyObj(), has_factory='From a lambda', neither=None) + >>> NoInitFields().has_default is NoInitFields().has_default + True + + >>> NoInitFields(1) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + TypeError: NoInitFields.__init__() takes 1 positional argument but 2 were given + + >>> NoInitFields(has_default=1) # doctest: +ELLIPSIS + Traceback (most recent call last): + TypeError: ...has_default... + >>> NoInitFields(has_factory=1) # doctest: +ELLIPSIS + Traceback (most recent call last): + TypeError: ...has_factory... + >>> NoInitFields(neither=1) # doctest: +ELLIPSIS + Traceback (most recent call last): + TypeError: ...neither... + """ + has_default : object = cython.dataclasses.field(default=DummyObj(), init=False) + has_factory : object = cython.dataclasses.field(default_factory=lambda: "From a lambda", init=False) + # Cython will default-initialize to None + neither : object = cython.dataclasses.field(init=False) + + def __post_init__(self): + if not cython.compiled: + # Cython will default-initialize this to None, while Python won't + # and not initializing it will mess up repr + assert not hasattr(self, "neither") + self.neither = None |