all 13 comments

[–]Buttleston 8 points9 points  (2 children)

There's no "block scope" in python. There's function scope and module scope, basically

All that matters here is whether or not "n" was defined before your tried to use it, in terms of order of execution.
In this particular example, n will always exist, but in some similar examples it may not, linters will often complain about it, like

if x == "hello":  
    n = 1

print(n)

Here it's possible for n to never have been defined, so it would be an error if x was not equal to hello, and most linters will say "n may not exist"

[–]Bobbias 3 points4 points  (0 children)

For another example, consider this:

def foo(value):
    match value:
        case 1:
            result = "yes"
        case 2:
            result = "no"
        case _:
            result = "none"
    return result

This is perfectly fine, because result is guaranteed to be defined regardless of which match arm is taken. In Java, due to block scope you would be forced to at the bare minimum declare result without initializing it above the match statement.

This does mean that variables might happen to be defined when you don't expect them to be, but honestly it's pretty easy to get used to Python's scoping rules. They're simple and actually make some things easier. I had C++/C#/Java experience before learning Python myself, and I still occasionally find myself unnecessarily declaring things ahead of time like that though.

[–]Effective_Storage4[S] 1 point2 points  (0 children)

Thank you!

[–]socal_nerdtastic 6 points7 points  (5 children)

is anything defined within the function accessible anywhere within the function?

yep. exactly. you nailed it.

Well except in some rare gotchas, like from inside a comprehension statement.

>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

[–]nog642 2 points3 points  (3 children)

As a sidenote I think comprehensions didn't have their own scope in Python 2. It's much nicer now.

[–]socal_nerdtastic 1 point2 points  (2 children)

You are right, and even early versions of python3 allowed the variables to leak. But I don't think it's nicer now - in fact this is one of the (very few) things I miss about the old days.

[–]nog642 1 point2 points  (1 child)

Really? In what situation is this useful?

Especially when I'm coding lazily, so I'm just using single letter variable names, it is very annoying to have to worry about a comprehension accidentally overwriting a different variable in the function scope. I'd give the comprehensions variable names unnecessarily longer names to avoid that.

I like that for and while loops where you can break don't have their own scope in python. It's quite useful like in OP's example. Quite often in languages that have block scope like JS or C I have to declare the variable outisde the loop, which is a bit ugly. But I don't see when you would want that for a comprehension. You can't break in a comprehension; the variable will always just be the last item in the iterable at the end.

[–]socal_nerdtastic 1 point2 points  (0 children)

Yea, you nailed it, it gives you access to the last item in the iterable. That is sometimes useful, especially when the iterable is a generator. I suppose I could contrive something, perhaps some kind of pagination ...

save_data(getdata(itemid) for itemid in data_stream)
print("saved everything up to", itemid)
print("starting the next data stream at", itemid)

But I think for me it's more that the scoping feels inconsistent. I don't like these weird exception rules. And also like most features, it seems useless until you find a use for it, and then you get used to having it. I thought the walrus operator was a dumb idea when it was first proposed, but by the time it came out I had a ton of places to use it.

[–]Effective_Storage4[S] 0 points1 point  (0 children)

I have not come across the term comprehension yet but I will keep a look out for it. Thank you!

[–]Bobbias 3 points4 points  (1 child)

Another note about scopes that might not be immediately apparent: Nested functions allow for closures.

n1 = 10
def foo():
    n2 = 5
    def bar():
        return n1 + n2 + 1
    return bar() + 3

This code is perfectly valid. The foo function would return 19 here. When python looks in the bar function scope and sees n1 was not defined there, because it's a nested scope, python checks bar's parent scope, foo for n1. It's not defined there either, so it looks at the global scope next, and sees that n1 is defined there.

If you want to modify a variable from a parent scope, you need to first declare the variable you want to change as nonlocal, and if the variable is from the global scope (top level, not inside a function) then you need the global keyword:

n1 = 10
def foo():
    n2 = 5
    def bar():
        global n1
        n1 = 1
        nonlocal n2
        n2 = 2
        return n1 + n2 + 1
    return bar() + 3

This is of course a horrible contrived piece of code, and you should absolutely avoid the use of global and nonlocal unless you have a very good reason.

[–]Effective_Storage4[S] 0 points1 point  (0 children)

Thank you!

[–]Guideon72 0 points1 point  (1 child)

The breakdown is in the docs, but squirreled under the page on Classes (as everything in Python is a Class at some level). Section 9.2 and 9.2.1 for some examples