36

I am using pycharm and it lists out all the errors/warnings associated with the code. While I understand most of them I am not sure of this one "Shadows name xyz from outer scope". There are a few SO posts regarding this: How bad is shadowing names defined in outer scopes? but then they seem to be accessing a global variable.

In my case, my __main__ function has a few variable names and then it is calling another function sample_func which uses those variable names again (primarily the loop variable names). I am assuming because I am in a different function, the scope for these variables will be local, however the warning seem to suggest otherwise.

Any thoughts? For your reference here is some code:

def sample_func():
    for x in range(1, 5):  --> shadows name x from outer scope
        print x

if __name__ == "__main__":
    for x in range(1, 5):
        sample_func()
Community
  • 1
  • 1
The Wanderer
  • 3,051
  • 6
  • 29
  • 53
  • 11
    You don't actually have a `__main__` **function**, just some module-scope code that's only executed if `__name__ == "__main__"`. Hence there's no local scope for the "main part"; the for loop takes place (and binds `x`) at module scope. – Ben Jan 06 '17 at 03:27
  • Thanks Ben for the explanation. – The Wanderer Jan 06 '17 at 22:53

5 Answers5

30

The warning is about the potential danger you are introducing by re-using these names at inner scopes. It can cause you to miss a bug. For example, consider this

def sample_func(*args):
    smaple = sum(args) # note the misspelling of `sample here`
    print(sample * sample)

if __name__ == "__main__":
    for sample in range(1, 5):
        sample_func()

Because you used the same name, your misspelling inside the function does not cause an error.

When your code is very simple, you will get away with this type of thing with no consequences. But it's good to use these "best practices" in order to avoid mistakes on more complex code.

krethika
  • 3,908
  • 1
  • 24
  • 27
  • 2
    This is a very good example. However, irrespective of how much you try you will always have some variable names that would be common. What do you suggest is a better way to scope out variable names? As Ned suggested, does using another function named `main()` scopes out the variables ? – The Wanderer Jul 22 '15 at 23:33
  • 3
    @TheWanderer yes by defining a `main` function, he has changed the scope of the original definition so that they no longer conflict. – krethika Jul 22 '15 at 23:36
  • 1
    Thanks mehtunguh this is very useful. Just one last quick question? My script takes a few arguments as input. Should I parse them inside `__main__` and then send as arguments to `main()` ? I know it is a very stupid question but still asking just to understand the best practices. – The Wanderer Jul 22 '15 at 23:41
  • That's my preference, and from what I've seen the most common way. – krethika Jul 22 '15 at 23:43
  • Nice try, but now realistic this scenario ? This PEP8 guideline is definitely due to paranoia. Most IDEs nowadays have built-in spell checkers for variable names. Hence one could supress this part of PEP8 safely. – nehem Dec 17 '18 at 04:25
17

The code inside of your if branch of your main function is actually in scope when you're inside of sample_func. You can read from the variable x (try it out). This is okay as you don't really care about it so you have a few options to move forward.

1) Disable shadowing warnings in pycharm. Honestly this is the most straightforward and depending on how experienced of a coder you are it probably makes the most sense (if you're relatively new I would not do this though.)

2) Put your main code into a main function. This is probably the best solution for any production level code. Python is very good at doing things the way you want to do them so you should be careful not to fall into traps. If you are building a module, having lots of logic at the module level can get you into sticky situations. Instead, something like the following could be helpful:

def main():
    # Note, as of python 2.7 the interpreter became smart enough
    # to realize that x is defined in a loop, so printing x on this
    # line (prior to the for loop executing) will throw an exception!
    # However, if you print x by itself without the for loop it will
    # expose that it's still in scope. See https://gist.github.com/nedrocks/fe42a4c3b5d05f1cb61e18c4dabe1e7a
    for x in range(1, 5):
        sample_func()

if __name__ == '__main__':
    main()

3) Don't use the same variable names that you're using in broader scopes. This is pretty hard to enforce and is kinda the opposite of #1.

Ned Rockson
  • 1,095
  • 7
  • 16
  • I tried accesing x inside the sample_func (before its for x in range statement) and received 'UnboundLocalError: local variable 'x' referenced before assignment". ` def sample_func(): print(x) for x in range(1, 5): print(x) if __name__ == "__main__": for x in range(1, 5): sample_func() ` –  Dec 26 '16 at 00:07
  • @SurestTexas That's correct functionality. In most languages (python being one of them) an exception will be thrown if your runtime reads from an uninitialized / undefined variable. – Ned Rockson Jan 03 '17 at 19:28
  • @NedRockson what I was saying is that I tried reading the `x` (as you suggested), and I was not able to read from it (per your 'try it out'). I tried it and you CAN NOT read the outer scope's `x`. –  Jan 04 '17 at 21:14
  • @SurestTexas I guess my answer wasn't clear but I was pointing out in the original question's code that `x` was available within the `sample_func` prior to it being declared in the function. Apologies for lack of clarity! – Ned Rockson Jan 05 '17 at 06:33
  • Are you saying that the OP's code above has been modded and is not what you originally responded to? ..Because what is currently showing in the OP's code above is what I was using for my test. I just added a `print(x)` statement between lines one and two of the OP code above. That attempt yielded the error I described. I.e., I attempted to access the outer scope `x` from within sample_func's code before sample_func defines it in its for loop...? –  Jan 05 '17 at 17:27
  • You're right! It looks like the interpreter is being smart about x being used in that for loop and automatically shadowing. Thus, if you see the code here: https://gist.github.com/nedrocks/fe42a4c3b5d05f1cb61e18c4dabe1e7a it works, however when doing the above (where it failed for you) it throws an error. Nice catch! – Ned Rockson Jan 06 '17 at 03:14
  • 1
    Because the name `x` is written in `sample_func`, it's a local variable over the whole scope of that function; the module global `x` is inaccessible, even before the local `x` actually has a value. If `x` is only ever read in the function, it'll read the global `x`. – Ben Jan 06 '17 at 03:23
  • ...understanding ..deepened. Thanks! –  Jan 06 '17 at 03:31
2

It is just a warning, as explained in the linked question there are times when it can cause issues but in you case x is local to your function. You are getting the warning because of the x inside your if __name__ == "__main__": being in globals. It won't have any effect on the x in your function so I would not worry about the warning.

Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
2

I know this is an old thread and this isn't appropriate for the problem the asker was trying to find out about, but I was searching for an answer to why PyCharm was showing me a 'Shadows name from outer scope' message on a complex if/elif statement block...

It turns out I had capitalised some global variable names at the start of the function but used lower case in my if/elif block much further down in the function.

School boy error I know, but once I corrected this, the 'Shadows name from outer scope' message in PyCharm disappeared and the variables stopped showing as greyed out...

So the lesson I learned is that this PyCharm message may be caused by something as simple as a upper/lower case error in a variable name...

I only realised the problem while I was breaking the function into three functions to see if this would remove the 'Shadows...' error, as I was thinking I had an issue with indentation and this was causing the problem!

This may help another newbie who is scratching their head wondering why they are getting this error :-)

Mark Smith
  • 757
  • 2
  • 16
  • 27
1

I was running into this warning for an argument in a method named year, but no other variable was sharing that name. I then realized that it was because of the line from pyspark.sql.functions import * which was importing a year variable. Changing this to only import the functionality we needed go rid of the warning.

David Baucum
  • 2,162
  • 24
  • 25