python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.35k stars 2.81k forks source link

Issue initialising dataclass with getters and setters #9779

Open andrewblance opened 3 years ago

andrewblance commented 3 years ago

When using mypy with a dataclass that includes getters and setters it throws an error. It returns Name 'byr' already defined on line 5.

To Reproduce

To reproduce use mypy with this script:

@dataclass
class Passport:
    byr: int

    @property
    def byr(self) -> int:
        return self._byr

    @byr.setter
    def byr(self, value) -> None:
        if int(value) < 1920 or int(value) > 2002:
            raise Exception("Birth year must be between 1920 and 2002")
        self._byr = int(value)

Expected Behavior

I would have expected it to accept this, as this seems to be a standard way to handle getters and setters.

Your Environment

97littleleaf11 commented 3 years ago

I quickly tracked down the error msg and found that the property byr is marked as an OverloadedFuncDef which leads to a name redefinition error.

Related issues: https://github.com/python/mypy/issues/6493

97littleleaf11 commented 3 years ago

Probably related issues: https://github.com/python/mypy/issues/6715 https://github.com/python/mypy/issues/7918

gbrennon commented 2 years ago

do we have any solution for this?

erictraut commented 2 years ago

Here's a possible workaround: use a __post_init__ method.

@dataclass
class Passport:
    byr_init: InitVar[int]

    @property
    def byr(self) -> int:
        return self._byr

    @byr.setter
    def byr(self, value: int) -> None:
        self._byr = int(value)

    def __post_init__(self, byr_init: int):
        self.byr = byr_init
SubaruArai commented 2 years ago

as a workaround, we can ignore the getter:

@dataclass
class Passport:
    byr: int

    @property  # type: ignore
    def byr(self) -> int:
        return self._byr

    @byr.setter
    def byr(self, value) -> None:
        self._byr = int(value)

While mypy won't see the getter/setter it's (probably) better than nothing.

BUT we can't let mypy see other default types:

@dataclass
class Passport:
    byr: int

    @property  # type: ignore
    def byr(self) -> int:
        return self._byr

    @byr.setter
    def byr(self, value: typing.Union[str, None]) -> None:  # mypy won't recognize this ``None``
        if value is None:
            self._byr = 0
        else:
            self._byr = int(value)