The culprit is not in the global command but in the way you imported the variable. To get the result you originally expected change the import this way:
File test2.py
import test1
def f():
test1.a = 2 * test1.a
File test3.py
import test1
from test2 import f
f()
print(test1.a)
Explanation
In test2 when you do from test1 import a you are creating a global variable a in the current module (test2.a) bound to the same object as the variable a in the module test1 (test1.a) but these two variables are different variables. Assignment to a in test2 changes just to what test2.a is bound to, test1.a is still bound to the original object 100.
When you do import test1. Then you can refer to the original variable as test1.a and you can re-bind it.
Yes, that was a super explanation from VĂĄclav.
I ran it just to have a look and put the example in my âtrainingâ library.
Itâs hard to tell who has coding experience and who is venturing into coding with Python, butâŠ
Luc, you may recognize this as an example of namespace.
It seems possible that youâre fairly far along if youâre importing and linking modules that you wrote. Thanks for inspiring the lesson! Questions like this are what make discuss.python.org a great place to spend time!!
EDIT: I just ran across a topic on global vs. local namespace HERE. The scope of that discussion is functions within a single module but that topic is closely related to this one.
Summarizing the Execution model from Python 3.10.5 documentation:
The import statement of the form from ... import * binds all names defined in the imported module, except those beginning with an underscore.
âź
Each assignment or import statement occurs within [âŠ] a class or function definition or at the module level (the top-level code block).
âź
If a name is bound in a block, it is a local variable of that block, unless declared as nonlocal or global. If a name is bound at the module level, it is a global variable.
In the interest of clarity, can someone confirm thatâŠ
If a name is bound at the module level, it is a global variable.
âŠmeans that the scope of âglobalâ can never be higher than the immediate module, where âimmediateâ means where the execution thread is currently located?
Not to mix terminology glibly, but is it reasonable or at least useful to apply the analogy of âcasting the namespaceâ in this line? (After all, the purpose of explicitly invoking the namespace with namespace.func() or namespace.var is to pierce/escape the default scope.) This would explain why the import alone didnât bind âaâ at a level above the module without the out-of-scope reference to module test1.
This lineâŠ
from ... import * binds all names defined in the imported module
âŠseems to overstate the case with "âallâ" âbecause Lucâs code tried to bind âaâ with only the import.
In other words: a module cannot bind namespace for variables from a module that it imports. Is this correct? @steven.daprano@CAM-Gerlach@cameron?
#this works (gives '200'):
import ModuleTest1
from ModuleTest2 import f
f()
print(ModuleTest1.a)
---------------------------------
#this doesn't work (gives '100'):
from ModuleTest1 import a
from ModuleTest2 import f
f()
print(a)
---------------------------------
#this also does NOT work:
from ModuleTest1 import a as a #just being thorough;
from ModuleTest2 import f #...not a serious attempt.
f()
print(a)
---------------------------------
#nor does this:
from ModuleTest1 import * #I did halfway expect this to work
namespace #since it's a way to combine module namespaces
from ModuleTest2 import f
f()
print(a)
By Leland Parker via Discussions on Python.org at 26Jun2022 01:37:
Summarizing the Execution
model
from Python 3.10.5 documentation:
The import statement of the form from ... import * binds all names defined in the imported module, except those beginning with an underscore.
âź
Each assignment or import statement occurs within [âŠ] a class or function definition or at the module level (the top-level code block).
âź
If a name is bound in a block, it is a local variable of that block, unless declared as nonlocal or global. If a name is bound at the module level, it is a global variable.
In the interest of clarity, can someone confirm thatâŠ
If a name is bound at the module level, it is a global variable.
âŠmeans that the scope of âglobalâ can never be higher than the immediate module, where âimmediateâ means where the execution thread is currently located?
It means that the term âglobalâ, in Python, means a name bound at the
module level. So that when you go:
x = 1
def f(y):
z = x + y
return z
x is a global, and y and z are function locals. So we mean what
youâd naively expect of a global variable without thinking about modules
at all eg for a flat script.
There isnât really any âhigherâ namespace.
Not to mix terminology glibly, but is it reasonable or at least useful to apply the analogy of âcasting the namespaceâ in this line?
No? I have no idea what thatâs supposed to mean. To my mind, âcastâ is a
term I learnt with C, and largely means a type conversion, particularly
with pointers. Iâve seen people talk about things like:
s = "1"
i = int(s)
as a âcastâ, and I hate it. int() is like any other class
instantiation. It just âlooksâ like a type conversion. And it does
effectively a very similar thing. But a C style cast is a compiler level
thing.
Well, no? To use the above, namespace must be a name in the default
scope.
This would explain why the import alone didnât bind âaâ at a level
above the module without the out-of-scope reference to module test1.
Iâve lost track of what import youâre talking about. An import binds
names in the current scope. It is just a special purpose assignment
statement. A module level import assigns in the module namespace, one
inside a function binds in the function namespace:
import csv # binds "csv" as a module-level aka global name
def f():
import json # bind "json" as a function local name
This lineâŠ
from ... import * binds all names defined in the imported module
âŠseems to overstate the case with âallâ because Lucâs code tried to bind âaâ with only the import.
In other words: a module cannot bind namespace for variables from a module that it imports. Is this correct?
Well, not a namespace. Itâs an assignment. Consider:
Module A:
x = {'a': 2}
In the interpreter:
>>> import A # binds the module A to the local name "A"
>>> print(A.x['a'])
2
>>> from A import x # local "x" now a reference to the dict in A
>>> print(x['a'])
2
>>> x['a'] = 3
>>> print(x['a'])
3
>>> print(A.x['a'])
3
>>> x = {'a': 9} # "x" now bound to a _new_ dict
>>> print(x['a'])
9
>>> print(A.x['a']) # A.x has not been rebound
3
By Cameron Simpson via Discussions on Python.org at 26Jun2022 04:40:
x is a global, and y and z are function locals. So we mean what
youâd naively expect of a global variable without thinking about modules
at all eg for a flat script.
One thing I particularly rely on is its âttyâ mode, which writes
directly to the current terminal when active. This is particularly handy
when debugging a test suite. For example, pytest intercepts stderr
and ⊠prints it at the end, or drops it on the floor or something. If
Iâm running my (failing) tests that way, the X() output still comes up
immediately in bright yellow. Ideally, usefully just before the test
explodes.
So my dev environment (these days, usually configured with direnv)
sets these modes with environment variables. Using direnv, these .envrc files:
so locally thereâs some SPLINK* envvars associated with the code Iâm
working on, and the parent dir has CS_X_* envvars setting my usual
debug modes, common to all my checkouts. The 3 above:
make X() write to /dev/tty (bypassing any stderr interception,
such as test suites and command line redirections)
make X() write bright yellow messages, easy to see in the output
stuffs the name X into the builtins namespace! which means I can
just put X(...) calls in other modules without bothering with an import
And that last is the tie in to this namespace discussion
I donât think Discuss truncated your post. I got everything starting with âWell, Iâm a lazy typistâ and ending with your signature âCheers Cameronâ and everything in between.
I havenât run diff over the email I received and the updated version on the web UI, but I canât see any obvious missing text.
By Steven DâAprano via Discussions on Python.org at 27Jun2022 01:19:
I donât think Discuss truncated your post. I got everything starting
with âWell, Iâm a lazy typistâ and ending with your signature âCheers
Cameronâ and everything in between.
I havenât run diff over the email I received and the updated version on
the web UI, but I canât see any obvious missing text.
Yeah, me too. This morning. But i received the very truncated version
last night. Today it is complete.
I donât know about you, but Iâve got the mutt remove-duplicates hook
enabled. Iâm wondering if discourse sent out a revised version with the
same message id after I edited it?
Like the other two Pythonistas much wiser than I, I am similarly confused by what specifically you are asking here. However, something important to keep in mind is that in Python, there is actually not really such a thing as a âvariableâ in this sense, at least with the semantics as implemented in C and many other languagesâthat a variable designates a specific memory location, rather than just a name, and assigning to that variable directly modifies that same underlying memory.
Instead, (and I assume youâre familiar with the basics, but just to review) Python has names which can be bound to objects, which may be somewhat relevant to helping make this more understandable (depending on what specifically you are asking about). These objects, in turn, live at particular memory locations, related to their id(); two objects with the same id occupy the same memory, and in fact are the same object (object_1 is object_2), even though they may have different names; conversely, the same name may point to different objects (i.e. with different id()s) in different scopes, or different points in time within the same scope.
Moreover, performing âassignmentâ on that name actually binds a new object to it; it doesnât touch the original object or its underlying memory, so any other names (or other references) that refer to the same object still point to the original. The only way to actually change the object itself and its underlying memory, so that other references to it reflect the change, is to mutate it in place without binding (âassignmentâ).
So, putting this all together:
In the OPâs original example, from test1 import a is roughly equivalent to a = __import__("test1").a, i.e. binding the object referenced by the module-level attribute test1.a directly to the name a in the test3 module.
However (under normal conditions) modules are only executed once on the first import and cached in sys.modules for subsequent imports from other files in the same interpreter thread.
Therefore, initially, all names bound using from test1 import a refer to the same object, that created in the test1 module and bound to the module-level global name a.
As such, if the object bound to a is mutated (e.g. appending to a list), then all names that refer to it will reflect the change.
However, if the module-level name is instead rebound to a new object (which is indeed what test1.a = 2 * test1.a), then it will no effect on the existing object referred to by the name a in the various module namespaces
Thus, the code in the OPâs post shows no change to the object bound to the name a in test3, because that object was not modified; rather, a new object was created and bound to the name a in test3.
import test1 is roughly equivalent to test1 = __import__("test1"), i.e. binding the module object created by executing the test1 module to the name test1 at the global scope of the test3 module.
However, normally, modules are only ever imported once and cached in sys.modules after the first import from other files in the same interpreter thread.
Therefore, while their names may be different in each module, all imported instances of the module point to the same underlying object
In turn, if the underlying module object is mutated, which includes modifying or rebinding attributes (test1.a = 2 * test1.a), then all names referencing that object (i.e. all instances of that imported module) reflect the change.
Thus, the code in @vbrozik 's post works as you expect and the modified module-level attribute is visible to test3âand any other modules that import it, for any code that executes after f() that references the top-level module object.
However, this can be very difficult to understand and reason about and easy to introduce confusing and hard to debug errorsâwhat if f() gets executed twice? What if other modules using it donât expect the change? Therefore, this sort of global state should be minimized or avoided if at all practical, and the state modified and shared as narrowly as possible.
Right, but to quote Jurassic Park, âYour scientists were so preoccupied with whether or not they could, they didnât stop to think if they should.â