PyCQA / flake8-bugbear

A plugin for Flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`B023` False positive

tylerlaprade opened this issue · comments

arr = ['a', 'b', 'c', 'd', 'e', 'f']
mydict = {} 
for i in range(0, len(arr)):
    def foo():
        mydict[arr[i]] = 0
    foo()

This example is obviously too simple to be realistic - our actual code has more logic in foo() and passes parameters to it, but is equivalent in terms of everything the rule cares about. We get an error of Function definition does not bind loop variable 'i'.Flake8(B023). This should not happen because foo is not a closure. It's never called outside the loop, and execution is never delayed. i should always have the correct value.

The reason we aren't immediately doing mydict[arr[i]] = 0 is because we have other logic that determines the parameters passed to foo().

Would accept a PR that somehow dives into the function scope to see i is use within the loop.

Just out of interest, does passing i as a parameter to foo cause the check to no longer flag this check?

@cooperlees It does not, this code doesn't get a warning:

arr = ['a', 'b', 'c', 'd', 'e', 'f']
mydict = {} 
for i in range(0, len(arr)):
    def foo(x):
        mydict[arr[x]] = 0
    foo(i)

However this approach isn't feasible for our actual usecase.

The same problem in

for i in range(5):
    def foo(a):
        return i + a  # B023
    print(foo(10))

Even with nonlocal I get B023

def bar():
    for i in range(5):
        def foo(a):
            nonlocal i
            return i + a  # B023
        print(foo(10))

I've to assign i to avoid B023

def bar():
    for i in range(5):
        def foo(a):
            nonlocal i
            i = i
            return i + a
        print(foo(10))

The warning is still technically correct about the binding issue in the function here. It might be out of scope for the checker to keep monitoring and see where the function is used?

Here's another workaround - bind the variable a default value:

arr = ['a', 'b', 'c', 'd', 'e', 'f']
mydict = {} 
for i in range(0, len(arr)):
    def foo(x=i):
        mydict[arr[x]] = 0
    foo()