51

I wonder why I can change global dictionary without global keyword? Why it's mandatory for other types? Is there any logic behind this?

E.g. code:

#!/usr/bin/env python3

stringvar = "mod"
dictvar = {'key1': 1,
           'key2': 2}

def foo():
    dictvar['key1'] += 1

def bar():
    stringvar = "bar"
    print(stringvar)

print(dictvar)
foo()
print(dictvar)

print(stringvar)
bar()
print(stringvar)

Gives following results:

me@pc:~/$ ./globalDict.py 
{'key2': 2, 'key1': 1}
{'key2': 2, 'key1': 2}  # Dictionary value has been changed
mod
bar
mod

where I would expect:

me@pc:~/$ ./globalDict.py 
{'key2': 2, 'key1': 1}
{'key2': 2, 'key1': 1}  # I didn't use global, so dictionary remains the same
mod
bar
mod
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jovik
  • 4,046
  • 6
  • 24
  • 24
  • I read [SO: Global keyword in Python](http://stackoverflow.com/questions/11867359/when-do-i-need-to-use-the-global-keyword-in-python) and [SO: Why is keyword global not required](http://stackoverflow.com/questions/14081308/why-is-not-the-keyword-global-not-required-in-this-case), so I know how dictionary works without `global`, but I still fail to understand why Python has different scope approach depending on variable type? – Jovik Jan 14 '13 at 17:54
  • 4
    You haven't understood the difference between mutable and immutable objects in Python. If you read http://docs.python.org/2/reference/datamodel.html (at least the subsection 3.1, don't skip parts of it just because you think you already know it) then it should become clear. – mmgp Jan 14 '13 at 18:03
  • 1
    There is nothing specific to Python 3 in this question. – Martijn Pieters Jan 14 '13 at 18:07

2 Answers2

57

The reason is that the line

stringvar = "bar"

is ambiguous, it could be referring to a global variable, or it could be creating a new local variable called stringvar. In this case, Python defaults to assuming it is a local variable unless the global keyword has already been used.

However, the line

dictvar['key1'] += 1

Is entirely unambiguous. It can be referring only to the global variable dictvar, since dictvar must already exist for the statement not to throw an error.

This is not specific to dictionaries- the same is true for lists:

listvar = ["hello", "world"]

def listfoo():
    listvar[0] = "goodbye"

or other kinds of objects:

class MyClass:
    foo = 1
myclassvar = MyClass()

def myclassfoo():
    myclassvar.foo = 2

It's true whenever a mutating operation is used rather than a rebinding one.

David Robinson
  • 77,383
  • 16
  • 167
  • 187
  • `stringvar = "bar"` is only ambiguous if you are considering a language other than Python. The only barely positive message someone can take from this is answer is the bottom-most link. – mmgp Jan 14 '13 at 18:10
  • 10
    @mmgp: I think you misunderstand me. I'm not saying the behavior is ambiguous or undefined. The OP was asking why Python behaves as it does, and I was explaining why it doesn't have a choice- because it can't know when you have the line `stringvar = "bar"` whether you mean to refer to the global variable or create a local variable. It therefore defaults to creating a local variable. – David Robinson Jan 14 '13 at 18:17
  • 1
    So if there is a local dictionary also called `dictvar`, would that get invoked first before the global one? – Jin Oct 06 '18 at 00:28
  • 1
    @Jin yes, that’s right! – David Robinson Oct 06 '18 at 01:00
13

You can modify any mutable object without using global keyword.

This is possible in Python because global is used when you want to reassign new objects to variable names already used in global scope or to define new global variables.

But in case of mutable objects you're not re-assigning anything, you're just modifying them in-place, therefore Python simply loads them from global scope and modifies them.

As docs say:

It would be impossible to assign to a global variable without global.

In [101]: dic = {}

In [102]: lis = []

In [103]: def func():
    dic['a'] = 'foo'
    lis.append('foo') # but  fails for lis += ['something']
   .....:     

In [104]: func()

In [105]: dic, lis
Out[105]: ({'a': 'foo'}, ['foo'])

dis.dis:

In [121]: dis.dis(func)
  2           0 LOAD_CONST               1 ('foo')
              3 LOAD_GLOBAL              0 (dic)     # the global object dic is loaded
              6 LOAD_CONST               2 ('a')
              9 STORE_SUBSCR                         # modify the same object

  3          10 LOAD_GLOBAL              1 (lis)    # the global object lis is loaded
             13 LOAD_ATTR                2 (append)
             16 LOAD_CONST               1 ('foo')
             19 CALL_FUNCTION            1
             22 POP_TOP             
             23 LOAD_CONST               0 (None)
             26 RETURN_VALUE  
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • I understand I can do it (and mechanism behind it). What bothers me is why Python makes exceptions for `global` statement? – Jovik Jan 14 '13 at 17:57
  • 1
    @Jovik: There is no exception being made. You are confused between variable assignment and value manipulation. You cannot do `var = 'string'`, `var.insert(0, 'othertext')` because a string is not mutable, you can only replace the value the variable points to. But you *can* alter a mutable. You can do `var1 = {}`, `var2 = var1`, `var2.update({'another': 'dict'})` and you'll see that `var1` reflects that same change. You changed a mutable value, not the variables that point to it. – Martijn Pieters Jan 14 '13 at 18:09