3

Another question does not explain why I can't change variable time_verf without using 'global' in the example (2) but still can do it to the list in the example (4).

On the resource I found that I can not change global variable from within a function, which is clearly illustrated by these examples:

from datetime import datetime, timedelta
time_verf = datetime.now()

I think I understand why the following is working (1):

def check_global():
    global time_verf
    clean_list = time_verf + timedelta(hours=12)  # время очистки листа
    if clean_list < datetime.now():
        list_verf.clear()
        time_verf = datetime.now()
    print('ok')

>> check_global()
<< ok

Next it throws exception when I comment out line with global keyword (2):

def check_global():
    # global time_verf
    clean_list = time_verf + timedelta(hours=12)  # время очистки листа
    if clean_list < datetime.now():
        list_verf.clear()
        time_verf = datetime.now()
    print('ok')

>> check_global()
<< Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 3, in check_global
UnboundLocalError: local variable 'time_verf' referenced before assignment

And then again can be again referenced without 'global' when line with assignment commented out(3):

def check_global():
    # global time_verf
    clean_list = time_verf + timedelta(hours=12)  # время очистки листа
    if clean_list < datetime.now():
        list_verf.clear()
        # time_verf = datetime.now()
    print('ok')

>> check_global()
<< ok

But why am I allowed to update list defined in the outer scope without using global (4)?

list = []

def check_global_list():
    list.append('check')
>> check_global_list()
>> list
<< ['check']
Vladimir Kolenov
  • 438
  • 1
  • 4
  • 14
  • I've seen this answered here on SO ... can't find the question right now ... the idea is that by having the assignment line in the code below the name "time_verf" is added to the locals(even before the first line of code is executed), I'll search for the post with the details – Lohmar ASHAR Jul 31 '19 at 09:19
  • Possible duplicate of [Python variable scope error](https://stackoverflow.com/questions/370357/python-variable-scope-error) – Lohmar ASHAR Jul 31 '19 at 09:24
  • 1
    your function with the line commented out just calls the variable, it doesn't try to modify. I think this is about mutability vs. immutability (lists are mutable, this is why your bottom example works (would also with e.g. a dict)) – FObersteiner Jul 31 '19 at 09:29
  • @MrFuppes I added numbers to the examples, could you please explain what example do you refer? – Vladimir Kolenov Jul 31 '19 at 09:35
  • 1
    @VladimirKolenov what I mean is that you are allowed to modify mutable objects such as a `list` inside a function without declaring it a `global` variable. This is what you show in (4). On the other hand, an immuatble object such as `datetime` you have to declare as a `global` to modify it inside a function (or pass it as an argument/keyword), see your examples (1)-(3). – FObersteiner Jul 31 '19 at 09:39
  • @MrFuppes thanks, was not thinking about mutability. Do you know how the example three works? Does python interpreter analize full function body and if sees there definition of variable which name matches variable name from outer scope, will not refer the variable from outer scope without 'global' even if usage of such variable happens prior to definition later? – Vladimir Kolenov Jul 31 '19 at 09:49
  • 1
    @VladimirKolenov, yes I think BoarGules gave a good explanation on that. It's about the chronological sequence (top to bottom of your code) in which you define variables. I've added some words on mutability since this can cause some confusion in a script. – FObersteiner Jul 31 '19 at 12:34

3 Answers3

2

When you comment out the global time_verf statement in line 2,

1: def check_global():
2: # global time_verf
3: clean_list = time_verf + timedelta(hours=12)  # время очистки листа
4: if clean_list < datetime.now():
5:     list_verf.clear()
6:     time_verf = datetime.now()

line 6 assigns a value to a local variable time_verf. It is local because assigning it creates it as a local variable. You get an error on line 3 because it refers to the local variable that your code creates in line 6. If you did not have that assignment then time_verf would be global by default.

But whether a variable is global or not does not depend on the order of execution. You appear to expect that because line 3 on its own would make time_verf global by default, then it becomes global and remains global in line 6. But that isn't the way local variables work. The presence of line 6 changes the meaning (and correctness) of line 3. The interpreter examines the entire scope of your function and creates local variables for any names that the code assigns values to. So because of line 6, time_verf is local, even if line 3 on its own would make it global.

There are very good reasons for this behaviour. Suppose line 3 were enclosed in an if-test. Then, according to the behaviour you seem to expect, the variable would be global if the if-test were true, but local if the if-test were false.

BoarGules
  • 16,440
  • 2
  • 27
  • 44
2

The resource you found states "If we need to assign a new value to a global variable then we can do that by declaring the variable as global.", the keyword being "assign". You can access global variables, call methods on them, mutate them without declaring them as global.

It's when you need to assign to them that you need to declare them as global.

Roel Schroeven
  • 1,778
  • 11
  • 12
1

Edit: I agree with Roel Schroeven that the core issue here is assignment!

Nevertheless, BoarGules gave a good answer regarding variable lifetime imho. And in addition I think an important point to notice here is mutability vs. immutability. This especially refers to (4) of the question. While the following works fine

a = 2.72 # global scope, defined BEFORE the function
def modify():
    b = a + 3.14 # no assignmnet made to a (but to b in this case)
    return b # scope of b is local -> 'return' needed to make it available in the outer scope

In [106]: modify()
Out[106]: 5.86

this fails:

a = 2.72
def modify(): # assignment!
    a += 3.14 # you would need 'global a' before this to make it work
    return a

In [108]: modify()
UnboundLocalError: local variable 'a' referenced before assignment

While a can be called inside modify(), it cannot be modified since it is type float and thus an immutable object.

On the other hand if you do the same with a mutable object like a list, you get

a = [2.72]
def modify():
    a[0] = 3.14 # change the reference to another value...
                   # no return needed

In [110]: modify()
In [110]: a
Out[110]: [3.14]

It doesn't fail AND a changed, even outside the scope of the function! It will fail again if you call a previously undefined variable in the function. Note that a[0] = 3.14 is not an assignment of a to some value but changes a reference stored in a to another value. This is very important if you have multiple functions in a script and pass things around. For furter reading, there are good resources on the topic out there, e.g. this as a starter and maybe also on the python data model in the docs.

FObersteiner
  • 22,500
  • 8
  • 42
  • 72
  • 1
    IMO mutability vs. immutability is not the core of the issue. The main point is that global is required for **assignment**, and that is true for both mutable and immutable variables. `a = a + [3.14]` needs `global` even for lists. – Roel Schroeven Jul 31 '19 at 13:18
  • @RoelSchroeven, I agree with you that this is the core issue of the question. Made an edit to clarify. – FObersteiner Jul 31 '19 at 13:42