Why do directly imported variables behave differently than thoseattached to imported module?

Discussion in 'Python' started by Dun Peal, May 3, 2011.

  1. Dun Peal

    Dun Peal Guest


    Here's the demonstrating code:

    # module foo.py
    var = 0

    def set():
    global var
    var = 1

    Script using this module:

    import foo
    from foo import *

    print var, foo.var
    print var, foo.var

    Script output:

    0 0
    0 1

    Apparently, the `var` we imported from `foo` never got set, but
    `foo.var` on the imported `foo` - did. Why?

    Thanks, D.
    Dun Peal, May 3, 2011
    1. Advertisements

  2. Dun Peal

    Daniel Kluev Guest

    Because all names are references to some values, not other names (in
    CPython, it means all names are PyObject*, and point directly to the
    objects, not other pointers)

    When you do `from foo import bar` it assigns globals()['bar'] of
    current module to reference same value as `foo.bar`. Its now local
    namespace name, not `foo` namespace, and therefore functions in `foo`
    cannot modify this namespace.

    Since ints are immutable, when you do `var = 1` you create new object
    of type int, and re-assign `var` name to point to new object.

    `foo.var`, on other hand, is a way to access `foo`'s own namespace, so
    its exactly same name as globals()['var'] of `foo`.
    Daniel Kluev, May 3, 2011
    1. Advertisements

  3. Dun Peal

    Chris Rebert Guest

    Because imports (and assignments generally) bind names to values, they
    don't alias names to other names.

    from foo import *

    can be thought of as essentially doing:

    import foo
    set = foo.set
    var = foo.var
    del foo

    So the new, separate name __main__.var gets the current value of
    foo.var at import-time, which is the integer 0.
    You then call foo.set(), which re-binds foo.var to a new value (i.e.
    1) rather than mutating the existing value (which would be impossible
    anyway since integers are immutable). This has absolutely no effect on
    __main__.var, which is an entirely separate binding.

    The behavior is comparable to that of function arguments. Values can
    be mutated, but re-binding names has only local effect:.... b = 1 # rebinds local name b
    .... b.append(99) # mutates passed-in value
    .... b = [42] # rebinds local name; has no outside effect
    ....[7, 99]

    Chris Rebert, May 3, 2011
  4. Dun Peal

    Mel Guest

    They're different because -- they're different. `foo.var` is defined in the
    namespace of the foo module. Introspectively, you would access it as
    `foo.__dict__['var']` .

    Plain `var` is in your script's namespace so you could access it as
    `globals()['var']` . The values given to the vars are immutable integers,
    so assignment works by rebinding. The two different bindings in
    foo.__dict__ and globals() get bound to different integer objects.

    Note too the possible use of `globals()['foo'].__dict__['var'] . (Hope
    there are no typos in this post.)

    Mel, May 3, 2011
  5. Dun Peal

    Terry Reedy Guest

    Your problem is reveal in the subject line. As discussed in many other
    threads, including a current one, Python does not have 'variables' in
    the way that many understand the term. Python binds names to objects.
    Binding statements (assignment, augmented assignment, import, def,
    class, and others with 'as' clauses) all bind names to objects.

    Suppose we both have address books. You copy all entries from my book to
    yours. Afterwards, I update an entry in my book. That does not change
    the now obsolete entry in your book.

    Module foo's dict now has two entries for 'var' and 'set' bound to int 0
    and a function. Note that 'set' is a builtin class name so not the best
    Script creates module '__main__'
    This binds 'foo' to module foo in __main_'s dict.
    This copies foo's dict to __main__'s dict.
    This changes foo's dict, rebinding 'var' to int 1. It does not change
    __main__'s dict. How could it? In the same way, 'var = 2' would leave
    foo's dict unchanged. __main__.var.
    If instead of 'from foo import *', you had written 'from foo import var
    as rav', then perhaps you would not have been so surprised that
    __main__.rav and foo.var have independent fates.

    You have discovered that using from x import * is a bad idea when module
    x has global names that get rebound.
    Terry Reedy, May 3, 2011
  6. Dun Peal

    harrismh777 Guest

    My two cents to add in addition to the correct accounts of [ Daniel,
    Chris, Mel] is that you might have a misunderstanding of var, generally.

    Python has no variables--- in the way you think of them in other
    languages. Python only has references.

    Var is not 'set' in the classical sense of the word...

    var is a 'reference' (think C pointer, sort-of) to an object... '0'
    and '1' are objects of type int and

    var=0 means create a reference to object int('0') called var.

    a=b=0 a is a reference to object '0'
    b is a reference to object '0'

    a does not reference b

    This rest of the discussion has to do with name spaces:


    harrismh777, May 3, 2011
  7. Dun Peal

    Dun Peal Guest

    OK, I understand now.

    `from foo import var` means "create a module-global name `var` inside
    the current module, and have it point at the object `foo.var` is
    pointing at (following its evaluation)".

    Naturally, regardless of whether `foo.var` ever changes, the global
    `var` of the current module still points at the original object
    `foo.var` was pointing to at the time of the `var = foo.var`

    Thanks, D.
    Dun Peal, May 3, 2011
  8. Dun Peal

    Dun Peal Guest

    P.S. now I have to ask: is there a symbolic reference in Python, i.e.
    a name foo that points to "whatever bar.baz is pointing at"?

    Thanks, D.
    Dun Peal, May 3, 2011
  9. Dun Peal

    Daniel Kluev Guest

    Well, you could easily simulate that with proxy object,
    class SymbolicReference(object):
    def __init__(self, ns, name):
    self.__ns = ns
    self.__name = name

    def __get(self):
    return self.__ns[self.__name]

    def __getattribute__(self, attr):
    return object.__getattribute__(self, attr)
    return self.__get().__getattribute__(attr)

    def __repr__(self):
    return self.__get().__repr__()

    def __str__(self):
    return self.__get().__str__()
    Daniel Kluev, May 3, 2011
  10. Here's a side point. What types will hold a reference to the enclosing
    module (or at least its dictionary)? Would it be possible to use a
    from import to load a module, then "lose" the module even though
    you're using objects from it?

    I am guessing that a function or class will hold such a reference,
    because otherwise it would be a bit awkward for them to use any sort
    of module-level state. Or do they not, and instead go look for their
    module in sys.modules?

    Chris Angelico
    Chris Angelico, May 3, 2011
  11. Dun Peal

    Chris Rebert Guest

    some_python_function.__globals__ is the exact reference you postulated.
    Classes don't seem to hold such references; their methods do though
    (by virtue of being function objects), which makes sense.

    Chris Rebert, May 3, 2011
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.