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

B023 False positive #380

Open
tylerlaprade opened this issue Apr 20, 2023 · 4 comments
Open

B023 False positive #380

tylerlaprade opened this issue Apr 20, 2023 · 4 comments

Comments

@tylerlaprade
Copy link

tylerlaprade commented Apr 20, 2023

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().

@cooperlees
Copy link
Collaborator

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?

@tylerlaprade
Copy link
Author

@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.

@rysson
Copy link

rysson commented Aug 28, 2023

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))

@jakkdl
Copy link
Contributor

jakkdl commented Oct 28, 2024

It's never called outside the loop, and execution is never delayed.

This is somewhat tricky to check for. I think the only reasonable way to go about this would be to save foo as a potential B023 upon encountering it, and then raise the error if

  1. foo is referenced outside the loop afterwards, or
  2. foo is referenced in any way other than as a direct call (e.g. blah(foo)) inside the loop.

until the end of the parent scope where we clear it as a potential B023.

Glancing at check_for_b023 it's already quite complex though

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

No branches or pull requests

4 participants