Skip to content

Declaring the existence of a variable while using --strict-optional #2179

Closed

Description

@davidfstr

Consider the following program:

computed_later = None  # type: str
computed_later = 'hello'
print(computed_later)

When typechecked with:

$ mypy --strict-optional test_computed_later.py

The following error is emitted:

test_computed_later.py:1: error: Incompatible types in assignment (expression has type None, variable has type "str")

In this program, the assignment computed_later = None is intended to be a declaration, as described in PEP 484 (Type Hints):

In stubs it may be useful to declare the existence of a variable without giving it an initial value. This can be done using a literal ellipsis:

from typing import IO
stream = ...  # type: IO[str]

In non-stub code, there is a similar special case:

from typing import IO
stream = None  # type: IO[str]

Type checkers should not complain about this (despite the value None not matching the given type), nor should they change the inferred type to Optional[...](despite the rule that does this for annotated arguments with a default value of None). The assumption here is that other code will ensure that the variable is given a value of the proper type, and all uses can assume that the variable has the given type.

It appears that --strict-optional is not treating the None-assignment as a declaration in this case, which is inconsistent with PEP 484. Thoughts?

Activity

  1. davidfstr commented on Sep 24, 2016

    @davidfstr
    ContributorAuthor

    For this particular example I was able to leave off the annotation to remove the error:

    computed_later = None
    computed_later = 'hello'
    print(computed_later)
    
  2. gvanrossum commented on Sep 24, 2016

    @gvanrossum
    Member

    Thanks for reporting this -- I think this is an area where mypy with --strict-optional is not yet compliant with PEP 484.

    Note that if you place that code inside a class body, it will be accepted. (It's possible that this was intentional, this behavior is somewhat controversial.)

  3. gvanrossum commented on Sep 24, 2016

    @gvanrossum
    Member

    Also, for people reading this, there's a subtlety with the PEP that few appreciate. While it allows this:

    x = None  # type: int
    

    it does not allow this:

    n = None
    x = n  # type: int
    

    IOW the initializer must be exactly the expression None, not any value of type None. (Even though of course at runtime they are the same object.)

    The reason is that we really treat

    x = None  # type: int
    

    as a special syntactic form, equivalent to PEP 526's initializer-less form:

    x: int
    
  4. ddfisher commented on Sep 27, 2016

    @ddfisher
    Collaborator

    Yeah, this is definitely a place where mypy isn't PEP 484 compatible yet. I think it's worth implementing undefined checking before allowing this, because otherwise people won't be able to rely on non-Optional types really being non-Optional.

    FWIW, the reason it works without any annotations is because mypy will do a limited sort of type inference on variables that are initially None, and because mypy will track the current type of unions. Here's what mypy is thinking linewise:

    computed_later = None  # computed_later is a partial None type
    computed_later = 'hello'  # computed_later now has type Optional[str], but has type str in the current context
    print(computed_later)
  5. added this to the milestone on Sep 28, 2016
  6. removed this from the milestone on Mar 29, 2017
  7. ilevkivskyi commented on Jul 6, 2019

    @ilevkivskyi
    Member

    I don't think we need to keep this open. Nothing will likely change in how mypy works with type comments and variable annotations will soon become the default way of things.

  8. jiridanek commented on Oct 11, 2020

    @jiridanek

    FFR, the PEP issue that lead to defining the special treatment of None was python/typing#61.

  9. amirsoroush commented on Apr 21, 2023

    @amirsoroush

    Up to this point(mypy=1.2.0 and Python=3.11) Mypy still doesn't allow this with either cases:

    from typing import IO
    
    stream1 = None  # type: IO[str]
    stream2: IO[str] = None
  10. alexisdrakopoulos commented on May 13, 2024

    @alexisdrakopoulos

    Is there any update on the best way to type hint delayed initialised variables?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions