Mypy: "already defined" errors when adding type annotations in multiple code paths

Created on 29 Jan 2016  路  13Comments  路  Source: python/mypy

While trying to debug something, I ended up adding type annotations for the definitions of an object in both code paths, and got a /home/tabbott/foo.py:10: error: Name 'child_pid' already defined with this (simplified) code:

if True:
    child_pid = os.fork() # type: int
    # Do stuff
else:
    child_pid = None # type: int

Since the types are the same, it seems like this shouldn't be an error.

This may be a variant of #1153.

Most helpful comment

There should be only one declaration of a given variable per scope.

This recommendations seems wholly unreasonable. Python itself has no such restriction. I feel lousy having to change my variable names just to make mypy happy.

All 13 comments

Hm, I think mypy actually has an intentional rule that you shouldn't annotate a variable twice, and I like that rule. So I am tempted to reject this one. The right fix would be to make the first annotation Optional[int].

Yeah, this is by design.

Closing this now. Feel free to reopen if you don't agree :-)

Somewhat related, this error also triggers even when del is used:

if True:  # For example, should not be const in practice.
    foo: int = 0
    print(foo)
    del foo
else:
    foo: int = 0
    print(foo)

In the following example it even reports an error: "Trying to read deleted variable 'foo'" on the last line.

if True:  # For example, should not be const in practice.
    foo: int = 0
    print(foo)
    del foo

foo: int = 0
print(foo)

I am using Mypy 0.610.

There should be only one declaration of a given variable per scope. And a function is one scope.

There should be only one declaration of a given variable per scope.

This recommendations seems wholly unreasonable. Python itself has no such restriction. I feel lousy having to change my variable names just to make mypy happy.

We are actively working on this restriction.

The initial implementation will likely only cover redefinition within a single block at the same nesting level, but it's possible that we'll extend it to handle other cases in the future.

The initial implementation will likely only cover redefinition within a single block

@JukkaL should this github issue be re-opened?

I just ran into this is in my code.

import typing, logging

def convertLog(log_level: typing.Union[str, int]) -> int:
    """
    usage:
       level = convertLog(log_level="DEBUG") 
       assert level == 10
    """
    if isinstance(log_level, str):
        log_level: int = logging._nameToLevel[log_level.upper()]
    return log_level

mypy complains about log_level already defined.
However, do note that a log_level enters the function either as an int or a str but can only come out as an int

@komuw You can simply remove that : int annotation, it is just not needed there.

Same Problem in the flowing to implementations
Note that implementation 2 is the way of annotating for loops per pep

so it definitely should be supported to have for loops with different types for the running variable

class Functionality():

    def __init__(self: Functionality, func: Callable[Info,Any]):
        super().__init__()
        self.func: Callable[Info,Any] = func
        self.pre_call: List[PreExpert] = []
        self.post_call: List[PostExpert] = []

    def __call__(self: Functionality, info):

        for expert in self.pre_call:
            if expert.can_work_with(info):
                expert.execute(info)

        ret = self.func(info)

        for expert in self.post_call:                    ##Problem
            if expert.can_work_with(info):
                expert.execute(info)

        return ret
class Functionality():

    def __init__(self: Functionality, func: Callable[Info,Any]):
        super().__init__()
        self.func: Callable[Info,Any] = func
        self.pre_call: List[PreExpert] = []
        self.post_call: List[PostExpert] = []

    def __call__(self: Functionality, info):
        expert: PreExpert
        for expert in self.pre_call:
            if expert.can_work_with(info):
                expert.execute(info)

        ret = self.func(info)

        expert: PostExpert  ##Problem
        for expert in self.post_call:
            if expert.can_work_with(info):
                expert.execute(info)

        return ret

You can try --allow-redefinition -- it will let you redefine some variables (though not always).

I'm having an issue where this redefinition check ignores pathways that will never happen at runtime, for example where the code takes a path after an if check, that ends with a return. Simplified example:

key: Union[int, str]

if isinstance(key, int):
    ...
    result: Tuple[int, str] = ...
    ...
    return result
...  # here the type of 'key' is 'str' only
result: Tuple[int, str] = ...  # here is the redefinition error, pointing at the line inside the if check above as the original definition
...
return result
Was this page helpful?
0 / 5 - 0 ratings