Skip to content

false report of multiple definitions #649

Description

@rbtcollins

The following code in six fails to type check:

try:
    advance_iterator = next
except NameError:
    def advance_iterator(it):
        return it.next()
next = advance_iterator

.../lib/python3.4/site-packages/six.py, line 501: Name 'advance_iterator' already defined

But advance_iterator can't be already defined: for NameError to be thrown, the lookup of next must have failed.

Whats the right way to handle this? Just mask it?

Activity

  1. refi64 commented on Apr 20, 2015

    @refi64
    Contributor

    This is a hack...but, whenever I encounter stuff like this, I do:

    try:
        advance_iterator = next
    except NameError:
        globals['advance_iterator'] = lambda it: it.next()
        next = advance_iterator
  2. JukkaL commented on Apr 29, 2015

    @JukkaL
    Collaborator

    This is a known, long-standing issue. Conditional definitions are often rejected because mypy is too picky.

    A potential fix would be to treat def more like an assignment, rather than a special 'function definition'.

  3. JukkaL commented on Nov 25, 2015

    @JukkaL
    Collaborator

    Another, slightly different example:

    if condition:
        from module import name
    else:
        name = None
    
  4. JukkaL commented on Nov 25, 2015

    @JukkaL
    Collaborator

    Merging with #212, which is similar.

  5. added a commit that references this issue on Nov 27, 2015
  6. JukkaL commented on Nov 27, 2015

    @JukkaL
    Collaborator

    Now some more conditional definitions are accepted by mypy. These are all okay:

    try:
        from m import CONST
    except ImportError:
        CONST = 'const'
    
    try:
        from m import f
    except ImportError:
        def f(): ...
    
    def f(): ...
    try:
        from m import f
    except ImportError: pass
    

    There's still a lot more work to do before mypy can deal with all interesting cases, but this should cover a good fraction of cases that used to fail.

  7. gvanrossum commented on Nov 28, 2015

    @gvanrossum
    Member

    Thanks, it's a bit better. There's still this case that doesn't seem to
    work right:

    import sys
    if sys.platform == 'darwin':
        import os
    else:
        os = None
    

    I get an error on each line mentioning os:

    /Users/guido/mypy_tests/mypy_imp.py:3: error: Need type annotation for
    variable
    /Users/guido/mypy_tests/mypy_imp.py:5: error: Name 'os' already defined
    

    On Fri, Nov 27, 2015 at 1:58 PM, Jukka Lehtosalo notifications@github.com
    wrote:

    Now some more conditional definitions are accepted by mypy. These are all
    okay:

    try:
    from m import CONST
    except ImportError:
    CONST = 'const'

    try:
    from m import f
    except ImportError:
    def f(): ...

    def f(): ...
    try:
    from m import f
    except ImportError: pass

    There's still a lot more work to do before mypy can deal with all
    interesting cases, but this should cover a good fraction of cases that used
    to fail.

    —
    Reply to this email directly or view it on GitHub
    #649 (comment).

    --Guido van Rossum (python.org/~guido)

  8. JukkaL commented on Nov 28, 2015

    @JukkaL
    Collaborator

    That should be pretty easy to support as well.

  9. gvanrossum commented on Nov 28, 2015

    @gvanrossum
    Member
  10. gvanrossum commented on Dec 11, 2015

    @gvanrossum
    Member

    OK, another case found in the wild:

    if ignore_case:
        correct_case = None
    else:
        def correct_case(args):  # E: error: Name 'correct_case' already defined
            ...
  11. JukkaL commented on Dec 11, 2015

    @JukkaL
    Collaborator

    I also have a long, unprocessed list of related issues from the Python 2.7 std library that I should analyze one of these days.

  12. added a commit that references this issue on Dec 31, 2015
  13. JukkaL commented on Dec 31, 2015

    @JukkaL
    Collaborator

    These now work:

    if x:
        def f(): ...
    else:
        f = g
    
    try:
        from m import f
    except ...:
        f = None
    

    These still don't work:

    if x:
        f = g
    else:
        def f(): ...
    
    if x:
        import m
    else:
        m = None
    
    if x:
        m = None
    else:
         import m
    
  14. gvanrossum commented on Jan 5, 2016

    @gvanrossum
    Member

    Here's another one that still doesn't work:

    if x:
        f = None
    else:
        def f(): ...
    

    Since it's the exact opposite of one that does work, how hard would it be
    to make it work?

    (In general I am beginning to become more interested in more symmetrical
    unification of branches when the type info gathered from one branch is of a
    clearly subordinate nature -- similar to x = [] if cond else [1, 2, 3],
    which IMO should infer a type of List[int] for x regardless of context.)

  15. JukkaL commented on Jan 6, 2016

    @JukkaL
    Collaborator

    I don't expect that supporting the f = None / def f(): ... case would be hard once we support f = g / def f(): ....

    [Here's some background for why these are hard. Mypy treats function definitions (and modules and classes) as special and different from variables. This is important for modules as it allows expressions like mod.ClassName to be bound early during semantic analysis, but less so for functions. We could unify functions and variables, but it would be pretty big change and there are some benefits to how things work currently.]

  16. gvanrossum commented on Jan 6, 2016

    @gvanrossum
    Member
  17. JukkaL commented on Jan 8, 2016

    @JukkaL
    Collaborator

    Treating a function definition like an assignment if it's already defined as a variable would work. Consider code like this:

    if x:
        f = None   # f variable
    else:
        def f(): ...
    

    We would treat it like this code:

    if x:
        f = None
    else:
        def _xyz(): ...   # _xyz must be unique
        f = _xyz
    
  18. JukkaL commented on Jan 8, 2016

    @JukkaL
    Collaborator

    These now work:

    if x:
        f = g
    else:
        def f(): ...
    
    
    if x:
        f = None
    else:
        def f(): ...
    
    if x:
        import m
    else:
        m = None
    

    The m = None / import m case is harder to support. I gave it a try but we should have a new type for modules for that.

  19. JukkaL commented on Jan 8, 2016

    @JukkaL
    Collaborator

    Okay, getting back to the original reported issue -- the fixes above don't actually solve it, as it also has the feature of both using a builtin in a module and redefining it. However, now there is fairly simple (though perhaps quite non-obvious) workaround:

    try:
        advance_iterator = __builtins__.next    # Note __builtins__!
    except NameError:
        def advance_iterator(it):
            return it.next()
    next = advance_iterator

    As we've pretty much implemented all the low hanging fruit improvements, I'm downgrading priority. Let me know if there are still common issues and I'll bump priority again.

    We could also close this issue and create separate issues for the remaining unsupported cases.

  20. added a commit that references this issue on Jan 8, 2016
  21. JukkaL commented on Jan 8, 2016

    @JukkaL
    Collaborator

    Closing this now, even though this is not fully fixed. The above issues cover some remaining missing features.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions