1

I am struggling to understand why a local function in Python 3 can seemingly modify global object attributes but not variables. If we consider object attributes to be variables attached to objects, this behavior appears very inconsistent and I cannot understand why the language behaves in this way.

  1. We can create some class and use a function to modify an attribute of that class without anything like a global declaration of that attribute.

If we have some code:

class Integer:
    def __init__(self, number):
        self.value = number

n = Integer(20)
print(n.value) # display value at initialization

def increase_n():
    n.value += 1

increase_n()
print(n.value) # display value after calling increase_n

Running this code would result in the following output:

20
21
  1. We cannot do the same with a variable. An explicit global declaration must be used.

If we have some code:

k = 6
print(k) # display value after assignment

def increase_k():
    global k
    k += 2

increase_k()
print(k) # display value after calling increase_k

Running the above code results in:

6
8

Note that foregoing the global declaration for the second example would result in an error.

I am hoping someone can enlighten me. I am sure there must be some fundamental misunderstanding on my part. Why are variables and attributes treated so differently? Why is this good/bad?

T. Kent
  • 11
  • 3
  • Also related: [Global dictionaries don't need keyword global to modify them?](https://stackoverflow.com/q/14323817/11082165) – Brian61354270 Jun 26 '22 at 14:57
  • In the first example, if you will do `n = Integer(21)` without `global` you will have the same problem... – Tomerikoo Jun 26 '22 at 14:58

1 Answers1

2

The thing that the global keyword does is allow you to push a local variable (i.e. a name) assignment into the global (outermost) namespace. By default, a variable exists in the namespace of the scope in which you assign a value to it; global overrides that.

You can reference a variable from an outer scope without needing any special keyword; global is only significant for the assignment operation. Once you have a reference to the associated object, if it's mutable, you can freely mutate it, regardless of how you got that reference or where the object was created.

No global declaration is needed to affect the attribute n.value because it lives within the namespace of the object that n references, not the global namespace. The fact that n itself is a variable in the global namespace is immaterial; you could do:

def increase_n():
    local_n = n  # local_n is local, n is still global
    local_n.value += 1

and it would have the same effect because local_n and n reference the same Integer object.

Samwise
  • 68,105
  • 3
  • 30
  • 44
  • This is a really good explanation and has definitely increased my understanding of what is actually occurring. Thank you! However, I still have some fogginess surrounding the problem. If k is actually an instance of the class int, then why can your explanation not be applied to the second example too? Is it because k += 1 is actually assigning a new value to k instead of incrementing it? Any additional insight appreciated. – T. Kent Jun 26 '22 at 15:08
  • That's exactly it! An `int` is immutable; when you do `k += 1` you're not mutating an int object, you're creating a new one and then assigning it to the name `k`. You can observe the difference by assigning another name to `k` (e.g. `j = k`) and then doing `k += 1`. `j` will still have the original value because it still points to the original object. When you do `n.value += 1` what you're doing is mutating `n` by calling `n.__setattr__('value', n.value + 1)`. – Samwise Jun 26 '22 at 15:56