4

I have part of a program written in Python 3.5 and started by testing the first two modules. I managed to isolate a problem in one of the modules where it appears that two global variables are switching back to their original values for no reason that I can understand. One of these global variables (event_count) is used in only a single function (grep shows the string "event_count" doesn't appear anywhere else in any of my *.py files), yet the value of the variable changes between calls to the function. If I add print statements for the other global variable in this module, it also reverts to it's original value at the same moment. Moving event_count to another module (replacing it with sensorlogic.event_count in eventcount() and moving the initialization to the other module) makes the behavior go away, so I have a fix but no understanding.

Here is all of the code that uses event_count, in module sensoreval:

event_count = 0

def eventcount(increment):
    global event_count
    print("entering eventcount, increment =", increment,
          ", event_count =", event_count)
    event_count += increment
    print("leaving eventcount, event_count =", event_count)
    return event_count

If I run the following code segment:

    e.setvalue(1)
    print("I am at marker #1")
    eventcount(0)

(the last action in e.setvalue() is a call to eventcount(0)) it produces this output:

entering eventcount, increment = 0 , event_count = 4
leaving eventcount, event_count = 4
I am at marker #1
entering eventcount, increment = 0 , event_count = 0
leaving eventcount, event_count = 0

I have tried trimming down the two modules to something of reasonable size, but the problem keeps going away when I do so. I'll keep working on that. Since I've never used Python 3 before, and only have a little Python 2.7 experience I assume I'm doing something stupid, I just have no idea what.

I believe that my example is different from some of the related posts that have been pointed out in that the variable event_count is global only so it will be static. It is used only in this single function. The string "event_count" doesn't appear anywhere else in this or any other module.


After many edit/rerun iterations, I have a managably small example that demonstrates what is happening. It involves two modules with a total of 8 lines of code. The first module, a.py, is __main__:

import b
c = 0
if __name__ == '__main__': 
    b.init()
    print("c =", c)

The second module is b.py:

import a
def init():
    a.c = 1

Running a.py produces the output:

c = 0

I expected c to still be 1 from the a.c = 1 in b.py.

Also, I tried to reduce this further by deleting the if __name__ == '__main__' from a.py, but then the example no longer runs:

Traceback (most recent call last):
  File "...\a.py", line 1, in <module>
    import b
  File "...\b.py", line 1, in <module>
    import a
  File "...\a.py", line 3, in <module>
    b.init()
AttributeError: module 'b' has no attribute 'init'

I can't explain that, either, but it seems likely to be related.


Following Mata's lead, I believe that the following code shows what's going on. There are three modules involved. a.py:

print("__name__ =", __name__)
import b
print("__name__ =", __name__)
def f(): pass
print(f)
if __name__ == '__main__':
    print("f is b.a.f?", f is b.a.f)

b.py:

import a

c.py:

import a
import b
print("__name__ =", __name__)
print("a.f is b.a.f?", a.f is b.a.f)

You can see the problem by running a.py, giving the result:

__name__ = __main__
__name__ = a
__name__ = a
<function f at 0x0000021A4A947840>
__name__ = __main__
<function f at 0x0000021A484E0400>
f is b.a.f? False

Running c.py so that __main__ isn't part of the import cycle results in:

__name__ = a
__name__ = a
<function f at 0x000001EA101B7840>
__name__ = __main__
a.f is b.a.f? True
Greg T
  • 43
  • 1
  • 6
  • Possible duplicate of [Using global variables in a function other than the one that created them](http://stackoverflow.com/questions/423379/using-global-variables-in-a-function-other-than-the-one-that-created-them) – rll Sep 26 '16 at 16:48
  • This looks relevant: http://stackoverflow.com/q/3536620/4996248 – John Coleman Sep 26 '16 at 16:50
  • There must be a clue in whatever it is that you trim that makes the problem go away. Also strange, your output says `event_count` not actually incremented. – Terry Jan Reedy Sep 26 '16 at 16:53
  • I'll wager, if you run `eventcount(4)` followed by `eventcount(0)` that you will get the result that you expect. So the error must lie in `e.setvalue()` – Rolf of Saxony Sep 26 '16 at 16:58
  • Quote: "the last action in e.setvalue() is a call to eventcount(0)". What is the point of that? – Rolf of Saxony Sep 26 '16 at 17:11
  • Originally `event_count` was a much more widely used global variable. I replaced it with a function in the hope of learning why things weren't working. @Terry: The function simply returns the value of `event_count` if its argument is zero, or changes `event_count` if the argument is non-zero. Thus `eventcount(0)` simply returns the value of `event_count` and as a side effect produces the printed output shown. @Rolf: This side effect is the only reason that there is a call in `e.setvalue()`, it was part of my tracking down the moment when the value changed. – Greg T Sep 26 '16 at 17:24
  • I would recommend against using evencount(0) to do something different than evencount(non-zero). Just make a new function if you want to return the value of even_count. Functions/methods should do one thing. – Albert Rothman Sep 26 '16 at 17:59
  • @Albert I completely agree, but I was trying to get the number of references to `event_count` down to an absolute minimum in order to isolate where the change in value was happening. This narrowed its use to a single function, but the variable is still changing unexpectedly. At this point I know how to avoid the problem, but I would like to better understand it to avoid stepping into it again. – Greg T Sep 26 '16 at 18:31
  • Is this run from `__main__`? What is `e`? The module may be imported twice, once as `__main__`, once as `some_module` (if it was named so), so you would end up with `__main__.event_count` being used by code from `__main__` and `some_module.event_count` from other modules that import it as `some_module`. – mata Sep 26 '16 at 19:43
  • Now that I've let Mata's comment sink in, he is exactly right. The first module was imported twice and references to it's global variables and functions from other packages pointed to the "other" copy, while the copy in `__main__` remained unchanged. Lesson: If there is a cycle of imports, then don't reference globals in `__main__` from other modules. Or even better keep `__main__` out of the cycle. – Greg T Sep 27 '16 at 06:07

1 Answers1

3

Let's take a look at your two-module example step-by-step. The behavior there is expected, but initially confusing and probably explains what is going on pretty well in the other cases.

If you run a as a script, it is not imported as a into sys.modules, but rather as __main__. The first statement is import b, which creates an empty module object sys.modules['b'] and begins initializing it.

The first line of b imports a again. Normally, a module object under sys.modules['a'] would be found, but in this case, you are running a as a script, so the initial import happened under a different name. Since the name of a is a and not __main__ this time, a.c is set to zero and nothing else happens.

Now execution returns to b. It now creates a function init, which sets sys.modules['a'].c to one. I wrote out the reference to the a module very explicitly because this is the root cause of your discrepancy.

Once b is imported, execution returns back to a, but not sys.modules['a']. The next line, c = 0 actually sets sys.modules['__main__'].c to zero. Hopefully you see the problem at this point. The next line calls b.init, which sets sys.modules['a'] to one. Then you print sys.modules['__main__'], which is zero, as expected.

To verify the correctness of this exposition, try adding a print statement

print(sys.modules['a'].c)

You will get 1. Also, sys.modules['a'] is sys.modules['__main__'].c will be False. The easiest way to get around this is not to initialize members of other modules in a given module's import.

Your specific case is documented here: http://effbot.org/zone/import-confusion.htm#using-modules-as-scripts.

Additional Resources

You can get a lot more information on the gritty details of the import system here: https://docs.python.org/3/reference/import.html. Various traps and caveats of imports are described here: http://python-notes.curiousefficiency.org/en/latest/python_concepts/import_traps.html.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264