Deeply understand the problem of variable assignment in Python

  • 2020-05-24 05:43:47
  • OfStack

preface

In Python, the variable name rules are affected by C, as are most other high-level languages, and the variable names are case-sensitive.
Python is a dynamically typed language, which means that you don't need to declare variable types in advance. The types and values of variables are initialized at the first moment of assignment.

Let's look at the following code first:


c = {}

def foo():
 f = dict(zip(list("abcd"), [1, 2 ,3 ,4]))
 c.update(f)

if __name__ == "__main__":
 a = b = d = c

 b['e'] = 5
 d['f'] = 6

 foo()

 print(a)
 print(b)
 print(c)
 print(d)

Output results:


{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4, 'f': 6}
{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4, 'f': 6}
{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4, 'f': 6}
{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4, 'f': 6}

If you're not surprised by this output, don't read it. In fact, the content of this article is very simple, so don't waste your valuable time on it.

Python is a dynamic language, the structure of a program can change as it runs, and python is a weakly-typed language, so if you've moved from a static, strongly-typed programming language to understand the assignment of Python, you might find some code a little confusing at first.

You might expect the output of the above code to look like this:


{}
{'e': 5}
{}
{'f': 6}

You might think that a hasn't been changed because you haven't seen any changes made to it; The changes in b and d are obvious; As for c, since it is changed within the function, you might think that c would be a local variable, so the global c would not be changed.

In fact, a, b, c, d all point to a block of memory space that holds a dictionary object. This is a bit like a pointer to c. a, b, c, d all point to the same memory address. So, no matter who you change, the other three variables will change. So why is c changed inside the function and not declared by global, but global c changed?

Let's look at another example:


>>>a = {1:1, 2:2}
>>>b = a
>>>a[3] = 3
>>>b
{1: 1, 2: 2, 3: 3}
>>>a = 4
>>>b
{1: 1, 2: 2, 3: 3}
>>>a
4

When b = a, a and b point to the same object, so when an element is added to a, b also changes. When a = 4, a no longer points to the dictionary object, but to a new int object (the integer in python is also an object). At this time, only b points to the dictionary, so b does not change when a is changed. This is just an indication of when assignment variables change qualitatively, and the above problem has not been solved.

So, let's take another example:


class TestObj(object):
 pass

x = TestObj()
x.x = 8
d = {"a": 1, "b": 2, "g": x}
xx = d.get("g", None)
xx.x = 10
print("x.x:%s" % x.x)
print("xx.x: %s" % xx.x)
print("d['g'].x: %s" % d['g'].x)

# Out:
# x.x:10
# xx.x: 10
# d['g'].x: 10

As you can see from the above example, if you only change the properties of an object (or change the structure), all the variables pointing to that object will change accordingly. But if one variable points back to an object, the other variables pointing to that object will not change. So, in the original example, c was changed inside the function, but c is a global variable. We just added a value to the memory pointed to by c, but did not point c to another variable.

Note that one might think that the last output in the example above should be d['g'].x: 8. The reason for this may be that you have taken out the value 'g' from the dictionary and renamed it xx, so xx is no longer relevant to the dictionary. The value in the dictionary is like a pointer to a region of memory. When you access key in the dictionary, you go to that region to get the value. If you take the value out and assign it to another variable, such as xx = d['g'] or xx = d.get ("g", None), you just make the variable xx also point to that region. That is to say, the key 'g' in the dictionary and the xx object point to the same piece of memory space. When we only change the properties of xx, the dictionary will also change.

The following example shows this point more intuitively:


class TestObj(object):
 pass

x = TestObj()
x.x = 8
d = {"a": 1, "b": 2, "g": x}
print(d['g'].x)
xx = d["g"]
xx.x = 10
print(d['g'].x)
xx = 20
print(d['g'].x)

# Out:
# 8
# 10
# 10

This is very simple, but if you don't understand it, you might not be able to read someone else's code. This point can sometimes be very handy for programming, such as designing a context to hold state throughout the program:


class Context(object):
 pass


def foo(context):
 context.a = 10
 context.b = 20
 x = 1

def hoo(context):
 context.c = 30
 context.d = 40
 x = 1

if __name__ == "__main__":
 context = Context()
 x = None
 foo(context)
 hoo(context)
 print(x)
 print(context.a)
 print(context.b)
 print(context.c)
 print(context.d)

# Out : 
# None
# 10
# 20
# 30
# 40

In the example, we can add the states that need to be saved to context so that they can be used anywhere during the entire program run.

In a final example, execute the external code:

outer_code.py


from __future__ import print_function

def initialize(context):
 g.a = 333
 g.b = 666
 context.x = 888

def handle_data(context, data):
 g.c = g.a + g.b + context.x + context.y
 a = np.array([1, 2, 3, 4, 5, 6])
 print("outer space: a is %s" % a)
 print("outer space: context is %s" % context)

main_exec.py


from __future__ import print_function

import sys
import imp
from pprint import pprint

class Context(object):
 pass

class PersistentState(object):
 pass


# Script starts from here

if __name__ == "__main__":
 outer_code_moudle = imp.new_module('outer_code')
 outer_code_moudle.__file__ = 'outer_code.py'
 sys.modules["outer_code"] = outer_code_moudle
 outer_code_scope = code_scope = outer_code_moudle.__dict__

 head_code = "import numpy as np\nfrom main_exec import PersistentState\ng=PersistentState()"
 exec(head_code, code_scope)
 origin_global_names = set(code_scope.keys())

 with open("outer_code.py", "rb") as f:
 outer_code = f.read()

 import __future__
 code_obj = compile(outer_code, "outer_code.py", "exec", flags=__future__.unicode_literals.compiler_flag)
 exec(code_obj, code_scope)
 #  Remove the properties of the built-in namespace and keep only those added in the external code 
 outer_code_global_names = set(outer_code_scope.keys()) - origin_global_names

 outer_func_initialize = code_scope.get("initialize", None)
 outer_func_handle_data = code_scope.get("handle_data", None)

 context = Context()
 context.y = 999
 outer_func_initialize(context)
 outer_func_handle_data(context, None)

 g = outer_code_scope["g"]
 assert g.c == 2886
 print("g.c: %s" % g.c)
 print(dir(g))
 print(dir(context))
 pprint(outer_code_moudle.__dict__)

conclusion

The above is the whole content of this article, I hope the content of this article to your study or work can bring 1 definite help, if you have questions you can leave a message to communicate.


Related articles: