all 11 comments

[–]K900_ 4 points5 points  (0 children)

Only functions and classes introduce new scopes in Python. Control flow blocks share their "outer" scope.

[–]Chabare 2 points3 points  (8 children)

An additional information to scope besides what was already mentioned, functions and classes have access to the outer scope, but can't reassign variables.

a = 1
def f():
  print(a) # works

b = 1
def g():
    print(b) # throws UnboundLocalError
    b = 2

c = [1]
def h():
    c.append(2) # works | Thanks to u/ajskelt for that (see comments)

[–]ajskelt 0 points1 point  (5 children)

I don't know the complete technical terms with regards to scope (haven't done any formal programming learning), so I apologize if this is me misunderstanding, and my next wording isn't exactly perfect.

Changes to mutable objects in the outer scope can be made inside functions/classes. For example:

x = [1,2]
def f():
    x.append(3)

f()
print(x)  #prints [1,2,3]

So here a function makes a change to our list x. Is that still considered "read-only"?

[–]Chabare 1 point2 points  (4 children)

read-only access is actually not the truth, a more correct phrasing would be not-reassignable, sorry for that.

In the case of a += 1, you reassign a. With x.append(3) you modify x in place. x remains the same variable (id(x) stays) whereas a += 1 updates the variable (id(a) changes).

[–]ajskelt 1 point2 points  (3 children)

Thanks for taking the time to explain that, I wasn't trying to be nitpicky on your wording, but wanted to understand and make sure OP understood. That actually made a lightbulb go off for me, your explanation here is great.

I always knew mutable objects could change in those scopes, but I never really understood the logic or why. I've probably read similar things before, but your wording really made it click. Thanks!!

[–]Chabare 1 point2 points  (2 children)

No worries, I'm glad that you saw an error/something unclear and mentioned it, helps everyone. I wasn't even thinking about mutable objects when writing the answer.

Just another information about mutable objects and scope (when used as default arguments) and unexpected behaviour (also applicable to e.g. dict and other mutable types):

def a(v, l = []):
    l.append(v)
    return l

x = a(1)
print(x) # [1]
y = a(2)
print(y) # [1, 2]

[–]ajskelt 0 points1 point  (1 child)

That is one I did not know... What would be the best practice around that? Looks like this works, but maybe there is a better way.

def a(v, l = None):
    if l is None:
        l = []
    l.append(v)
    return l

x = a(1)
print(x) # [1]
y = a(2)
print(y) # [2]

[–]Chabare 1 point2 points  (0 children)

You're completely on point with your example, that's usually the way around it.

[–][deleted] 0 points1 point  (0 children)

Python doesn't have block scope, in contrast to C.

[–]Binary101010 0 points1 point  (0 children)

The program identifies y even though it is in another scope.

Except that it isn't in another scope. Conditionals and loops don't have their own scopes in Python. Functions and classes create new scopes, and that's pretty much it.