1

I am trying to make functions in different files of a larger program send a message to each other. There is a function without a return statement. In a test example, if I do the following in one file, I can change a global variable and detect its change at run time:

one_file.py

import time
import threading
has_run = False

def processing_function():
    global has_run
    time.sleep(5)
    has_run = True

start = time.clock()

thread = threading.Thread(target=processing_function)
thread.start()

while True:
    print(has_run)
    time.sleep(0.5)
    if (10/3600) < time.clock() - start:
        break

When run, this will print False for a while then start printing True.

I tried to get this to work across two files like this:

file1.py

import time
has_run = False

def processing_function():
    global has_run
    time.sleep(5)
    has_run = True

file2.py

from file1 import processing_function, has_run
import time
import threading

start = time.clock()
thread = threading.Thread(target=processing_function)
thread.start()

while True:
    print(has_run)
    time.sleep(0.5)
    if (10/3600) < time.clock() - start:
        break

If you now run file2.py, it only prints False a lot of times.

Why is this imported global variable not being modified by the running process, and how can this be refactored so it works?

cardamom
  • 6,873
  • 11
  • 48
  • 102

2 Answers2

2

When you import the name has_run, you have created a new global in the current module named has_run that refers to the same object as file1.has_run, but an assignment to one of the names does not affect the other. If you want to see the changes made by processing_function, you need to continue accessing the name through the module.

while True:
    print(file1.has_run)
    ...

(This is because processing_function assigns a new value to its global, rather than mutating an existing value.)

You can observe this through a much simpler example. Consider a very simple module tmp1:

x = 3

Now see how from tmp1 import x creates a global variable whose value remains independent of tmp1.x:

>>> import tmp1
>>> from tmp1 import x
>>> x
3
>>> tmp1.x
3
>>> x = 6
>>> tmp1.x
3
>>> tmp1.x = 5
>>> tmp1.x
5
>>> x
6
>>>
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Seems like a `Lock` would also be required to control concurrent access to the global variable. – martineau Oct 15 '18 at 14:09
  • True, but I'd like to keep this answer focused strictly on the scoping issue. The race condition is really a separate question. – chepner Oct 15 '18 at 14:11
  • @chepner thanks, it works. I changed the import statement in file2 to be just `import file1` and changed the two places to be accessed through dots as per your answer. I will reflect on why this worked, as I proceed to implement it in the rest of the code, but your "detached" argument sounds intuitively correct. – cardamom Oct 15 '18 at 14:18
  • 1
    chepner: You're also completely glossing over the fact that what you say in your answer about "detaching" is only true for immutable objects. If the variable was, say, a `list`, it wouldn't be necessary to reference it through its module name. In other words, I think you may have "dumbed down" your answer too much. – martineau Oct 15 '18 at 14:21
  • @martineau For *assignments*, it doesn't matter; the names `tmp1.x` and `x` are simply different, and assignments to one do not affect assignments to the other. Since the names initially *refer* to the same object (I should edit my answer to emphasize that), then the result of *mutating* that object would be visible via either name. – chepner Oct 15 '18 at 14:58
  • I don't think you understood the point of my last comment—which was that if `x` was a `list` defined in `file1.py`, and `file2.py` did a `from file1 import x`, then changing plain `x` in file2 (i.e. `x[0] = 42`) _would_ affect its value globally since `list`s are mutable. That would not be the case if `x` was an `int` (which would become "detached"). P.S. I just verified this using Python 3.7.0. – martineau Oct 15 '18 at 16:36
  • @martineau That's not an assignment (though syntactically, it looks like one). You aren't assigning a new value to the name `x`, you're calling `x.__setitem__(0, 42)` to mutate the object *referenced* by `x`. – chepner Oct 16 '18 at 13:08
1

I believe the details of sharing the variable between Threads in Python you can find in this question.

Essentially, sharing any state (incl. variable) between threads requires synchronization (e.g. using threading.Condition). Otherwise, you are risking a race condition between one or more threads competing for access to the variable.

sophros
  • 14,672
  • 11
  • 46
  • 75
  • There may be a subtle race condition, but you can reproduce the problem (one of scoping) without the use of threads at all. – chepner Oct 15 '18 at 14:09