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

Error on conditional initialization of Final class variables #10736

Open
nelsyeung opened this issue Jun 29, 2021 · 5 comments
Open

Error on conditional initialization of Final class variables #10736

nelsyeung opened this issue Jun 29, 2021 · 5 comments
Labels
bug mypy got something wrong topic-final PEP 591

Comments

@nelsyeung
Copy link

nelsyeung commented Jun 29, 2021

Bug Report

Conditional initialization of a Final class variable is reported as a reassignment error on elif/else branches.

To Reproduce

tmp.py:

from typing import Final, Optional


class Foo:
    a: Final[Optional[int]]

    def __init__(self, v: int) -> None:
        if v == 0:
            self.a = None
        else:
            self.a = v  # error: Cannot assign to final attribute "a"  [misc]

Expected Behavior

No errors on the first correct assignment of a final variable regardless of branching.

Actual Behavior

$ mypy tmp.py
tmp.py:11: error: Cannot assign to final attribute "a"  [misc]
Found 1 error in 1 file (checked 1 source file)

Reported error: Cannot assign to final attribute "a" [misc] during the else clause, even though it's only the first assignment of the variable.

Your Environment

  • Mypy version used: 0.910

  • Mypy command-line flags: mypy tmp.py

  • Mypy configuration options from pyproject.toml (and other config files):

    [tool.mypy]
    show_error_codes = true
  • Python version used: 3.9.5

  • Operating system and version: macOS 11.4

Related

@nelsyeung nelsyeung added the bug mypy got something wrong label Jun 29, 2021
@AlexWaygood AlexWaygood added the topic-final PEP 591 label Mar 26, 2022
@randolf-scholz
Copy link
Contributor

Match-case statements are also affected: https://mypy-play.net/?mypy=master&python=3.11&gist=f0b81218a489585562acd1d8468c2f26

from typing import Final

class Foo:
    bar: Final[tuple[int, ...]]
    
    def __init__(self, bar: None | int | tuple[int, ...]) -> None:
        match bar:
            case None:
                self.bar = (-1,)
            case int():
                self.bar = (bar,)  # ✘ Cannot assign to final attribute "bar"  [misc]
            case tuple():
                self.bar = bar     # ✘ Cannot assign to final attribute "bar"  [misc]

@merc1031
Copy link

I have a similar issue / conundrum.
Is there anyway to (forward) declare the type of a variable that will be used in and exhaustive match?

https://mypy-play.net/?mypy=master&python=3.11&gist=10f92cdd6e414525c63d25ae251f9afa

def __init__(self, foo: None | int | tuple[int, ...]) -> tuple[int, ...]:
    bar: Final[tuple[int, ...]]  # ✘ Final name must be initialized with a value  [misc]
    match foo:
        case None:
            bar = (-1,)  # ✘ Cannot assign to final attribute "bar"  [misc]
        case int():
            bar = (foo,)  # ✘ Cannot assign to final attribute "bar"  [misc]
        case tuple():
            bar = foo  # ✘ Cannot assign to final attribute "bar"  [misc]
        case tuple():
            bar = foo  # ✘ Cannot assign to final attribute "bar"  [misc]

    return bar

@randolf-scholz
Copy link
Contributor

@merc1031 Try

from typing import Final

class Foo:
    bar: Final[tuple[int, ...]]

    def __init__(self, foo: None | int | tuple[int, ...]) -> None:
        bar: tuple[int, ...]
        match foo:
            case None:
                bar = (-1,)
            case int():
                bar = (foo,)
            case tuple():
                bar = foo
            case tuple():
                bar = foo
    
        self.bar = bar

@merc1031
Copy link

@randolf-scholz
If you notice my use case is without the class / instance variable. Only a function.

In a larger context it would be to protect from someone modifying bar before the end of the function

https://mypy-play.net/?mypy=master&python=3.11&gist=372aa35d3e4f02d35aabe38a3b709473

from typing import Final


def __init__(self, foo: None | int | tuple[int, ...]) -> tuple[tuple[int, ...], bool]:
    bar: Final[tuple[int, ...]]
    match foo:
        case None:
            bar = (-1,)
        case int():
            bar = (foo,)
        case tuple():
            bar = foo
        case tuple():
            bar = foo
    # more code happens here
    # protect from bar being reassigned
    return bar, True

@Avasam
Copy link
Contributor

Avasam commented Sep 25, 2024

I feel like there's 2 issues here.

  1. is the assignement to a Final attribute in a class' __init__. Duplicate of Allow Final attributes declared in class body if initialized in ctor #8982
  2. is the conditional assignement to a Final variable when all code paths include said assignement. And a ternary is not possible.
    For example:
if pytorch_is_available:
    import torch

    pytorch_gpu_is_available: Final = torch.cuda.is_available()
else:
    # We only assign once
    pytorch_gpu_is_available: Final = False  # type: ignore[misc]

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-final PEP 591
Projects
None yet
Development

No branches or pull requests

5 participants