Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assigning to an attribute with the same name as a dataclass InitVar raises has no attribute on 0.960 #12877

Closed
paw-lu opened this issue May 27, 2022 · 8 comments
Labels
bug mypy got something wrong topic-dataclasses

Comments

@paw-lu
Copy link

paw-lu commented May 27, 2022

Bug Report

On 0.960, if an attribute is typed as a dataclasses.InitVar and later tries to assign an attribute with the same name, mypy will complain on the assignment statement that the dataclass has no such attribute.

This does not occur on 0.950.

To Reproduce

import dataclasses
from dataclasses import InitVar


@dataclasses.dataclass
class Foo:
    a: int
    b: InitVar[int]

    def __post_init__(self, b: int) -> None:
        self.b = self.a + 1
$ pipx run --spec mypy==0.950 mypy --strict script.py
Success: no issues found in 1 source file

$ pipx run --spec mypy==0.960 mypy --strict script.py
script.py: note: In member "__post_init__" of class "Foo":
script.py:11:9: error: "Foo" has no attribute "b"  [attr-defined]
            self.b = self.a + 1
            ^
Found 1 error in 1 file (checked 1 source file)

Expected Behavior

I expected that mypy would not complain about an assignment just because it shares the same name as an InitVar.

Actual Behavior

mypy warns the user that the attribute does not exists in an assignment expression.

Your Environment

  • Mypy version used: 0.960
  • Mypy command-line flags: --strict
  • Python version used: 3.10
  • Operating system and version: macOS 12.3.1
@paw-lu paw-lu added the bug mypy got something wrong label May 27, 2022
@paw-lu paw-lu changed the title Assigning to an attribute with the same name as a dataclass InitVar raises has no attribute on 0.960 Assigning to an attribute with the same name as a dataclass InitVar raises has no attribute on 0.960 May 27, 2022
@erictraut
Copy link

PEP 557 refers to these as "init-only" fields. An init-only field is provided as arguments to the __post_init__ method but is otherwise not used as an attribute for the dataclass. I think mypy is correct in emitting an error here. Pyright does the same.

You should use a different name for the instance variable assigned in the __post_init__ method, a name that doesn't conflict with the name of the InitVar field.

@JelleZijlstra
Copy link
Member

I agree with Eric that this behavior is correct, but I don't recall a change that was meant to introduce this check. Perhaps we should add a new unit test to ensure mypy's behavior remains consistent.

@AlexWaygood
Copy link
Member

A unit test was added for disallowing this behaviour in #12798, so the error being emitted is expected behaviour, not a bug.

Note that if you use slotted dataclasses on Python 3.10+, this will also fail at runtime:

>>> from dataclasses import dataclass, InitVar
>>> @dataclass(slots=True)
... class Foo:
...     bar: InitVar[str]
...     baz: InitVar[int] = 0
...
>>> f = Foo('hi', 5)
>>> f.__slots__
()
>>> f.bar = 'bye'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> f.baz = 6
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object attribute 'baz' is read-only

@AlexWaygood
Copy link
Member

Closing this since it's a feature-not-a-bug, expected behaviour, and has a unit test.

@AlexWaygood AlexWaygood closed this as not planned Won't fix, can't repro, duplicate, stale May 27, 2022
@paw-lu
Copy link
Author

paw-lu commented May 28, 2022

Thanks @AlexWaygood for linking #12798. Appreciated! Seems like a straighforward case!

@JukkaL
Copy link
Collaborator

JukkaL commented May 30, 2022

Here's an example of how to work around this -- you can define two attributes, one is a regular one and the other is an InitVar:

from dataclasses import dataclass, InitVar, field

@dataclass
class D:
    _x: InitVar[int]
    x: str = field(init=False)

    def __post_init__(self, _x: int) -> None:
        self.x = str(_x)

@ksamuel
Copy link

ksamuel commented Mar 2, 2023

@JukkaL : this workaround mean you can do D(1), but not D(x=1), which is making things hard to read.

This means right now there is no clean way in dataclass to both have a transformed attribute you transform from initial values and slot at the same time.

It's not a huge problem, but I don't think this ticket should be closed until an alternative is proposed for that use case.

@AlexWaygood
Copy link
Member

AlexWaygood commented Mar 2, 2023

@JukkaL : this workaround mean you can do D(1), but not D(x=1), which is making things hard to read.

@ksamuel does this not work?

from dataclasses import dataclass

@dataclass(init=False)
class D:
    x: str
    def __init__(self, x: int) -> None:
        self.x = str(x)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-dataclasses
Projects
None yet
Development

No branches or pull requests

6 participants