Grady Shoemaker
Grady Shoemaker

Reputation: 31

Importing functions that declare global variables

EDIT: Alright, I pretty much figured it out (although it's not quite 100% what I wanted, it still works). It's the execfile() function, I can't believe I didn't know that was there.

I wasn't really sure how to phrase this question (and that's the reason my Google searches have turned up virtually nothing on this)... but here goes. I'm going to give a simplified version of what I'm trying to do. So let's say I have two files, main.py and extra.py. The latter looks something like this:

# extra.py

def setfoo(): # sets "foo" to 5 even if it is unassigned
    global foo
    foo = 5

So pretty straightforward if I run this in the console:

>>> setfoo()
>>> foo
5

Now let's head back over to main.py. I'll set up an import statement that imports everything from extra.py:

# main.py

from extra import *

setfoo()
print foo

Everything works fine up until the last line; however, when main.py tries to access foo, it doesn't recognize it since foo is actually stored under the file extra.py. If I import everything again after I run setfoo(), foo will be imported and everything will work fine. Like so:

# main.py

from extra import *

setfoo()
from extra import *
print foo

Alternatively, if I use a standard import statement instead of a from...import statement, there is no need to reimport everything, since the data from extra.py is being directly accessed rather than copied over. So this will work also:

# main.py

import extra

extra.setfoo()
print extra.foo

However, I really don't want to have to type out extra.setfoo() every time I want to run setfoo(), nor do I want to reimport extra.py each time I use said function. I would like to know if there is a workaround to this. Ideally, there would be a way to modify extra.py so that the code I set up in the original version of main.py works properly. (EDIT: It seems multiple people have misinterpreted this–I am willing to modify extra.py, it's main.py that I don't want to change, aside from the import statement at the top, to get this code to work.) If this isn't possible I would also consider using a different method of importing extra.py (I'm thinking something similar to PHP's include and require statements, in which the imported code is literally just copied and pasted into the file), but I would highly prefer that this modified import statement still be only one line of code, and not very lengthy. So in other words, using file handling plus an exec statement probably wouldn't be very convenient for this purpose.

Speaking of exec statements, I really don't care much at all how bad the coding practices I use are. I really just need a way to get this to work since the real version of this extra.py file is something I plan on using in almost all of my projects from now on, and I don't want to take up a lot of extra space in my code each time I import this file. Any help would be appreciated.

EDIT: It seems that a lot of people reading this aren't exactly clear on what I'm trying to accomplish, so here is the real version of my code. It's a syntax hack I put together to achieve "inline variable assignment". Here is the code (which works fine if you don't attempt to import it into another file):

class use_obj(object):
    def __rshift__(self, other): return other

def use(**kwargs):
    for var in kwargs: exec 'global ' + var + '; ' + var + ' = kwargs[var]'
    return use_obj()

The syntax looks like this:

print use(x = 5, y = 8) >> str(x) + " times " + str(y) + " equals " + str(x*y)

The code itself is pretty gross, but I've always wanted a way to perform inline variable assignment since I'm a big fan of inline if statements, list comprehensions, lambdas + reduce(), etc. The reason I can't simply have the function return the assigned variables is because use(x = 5, y = 8) is an expression (not a statement) that returns a weird object that I then shove into the code using the >> operator, and the weird object that the use() function returned magically disappears due to the way I set up the __rshift__() function.

I think the code would lose a lot of its beauty and novelty if it were to look like this:

print use(x = 5, y = 8) >> str(extras.x) + " times " + str(extras.y) + " equals " + str(extras.x*extras.y)

Upvotes: 2

Views: 870

Answers (3)

Sanket Sudake
Sanket Sudake

Reputation: 771

First how you can import your variable without modifying extra.py, if really want too, You would have to take help of sys module for getting reference to foo in extra module.

import sys
from extra import *

print('1. Foo in globals ? {0}'.format('foo' in globals()))
setfoo()
print('2. Foo in globals ? {0}'.format('foo' in globals()))
# Check if extra has foo in it
print('2. Foo in extra ? {0}'.format(hasattr(sys.modules['extra'], 'foo')))
# Getting foo explicitly from extra module
foo = sys.modules['extra'].foo
print('3. Foo in globals ? {0}'.format('foo' in globals()))
print("Foo={0}".format(foo))

Output:

1. Foo in globals ? False
2. Foo in globals ? False
2. Foo in extra ? True
3. Foo in globals ? True
Foo=5

Update for later usecase : Modifying extra.py which gets importer and updates its global variables,

# extra.py
import sys

def use(**kwargs):
    _mod = sys.modules['__main__']
    for k, v in kwargs.items():
        setattr(_mod, k, v)

Now importing in any file remains same,

#myfile.py
from extra import *
print use(x = 5, y = 8), str(x) + " times " + str(y) + " equals " + str(x*y)

Output:

None 5 times 8 equals 40

None appears as use function returns nothing.

Note: It would be better if you choose better pythonic solution for your usecase, unless you are trying to have a little fun with python.

Refer for python scope rules: Short Description of the Scoping Rules?

Upvotes: 1

Anil_M
Anil_M

Reputation: 11453

Without knowing details, one possible solution would be to return foo in extra.py setfoo() function instead of declaring as a global variable.

Then declare global foo in main.py and feed in value from external function setfoo()

Here is the setup

#extra.py

def setfoo(): # sets "foo" to 5 even if it is unassigned
    #global foo
    foo = 5
    return foo

#main.py

from extra import setfoo

global foo

foo = setfoo()
print foo

Result:

Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>> 
5
>>> 

EDIT - 1
OK, take-2 at this problem.

I don't endorse it, but if there is a specific need, If you add a variable to the __builtin__ module, it will be accessible as a global from any other module as long as it includes __builtin__ .

#extra.py

import __builtin__

def setfoo(): # sets "foo" to 5 even if it is unassigned
    global foo
    __builtin__.foo = 5


#main.py

from extra import *
setfoo()
print foo

Output:

>>> 
5
>>> 

Upvotes: 0

tdelaney
tdelaney

Reputation: 77347

Modules have namespaces which are variable names bound to objects. When you do from extra import *, you take the objects found in extra's namespace and bind them to new variables in the new module. If setfoo has never been called, then extra doesn't have a variable called foo and there is nothing to bind in the new module namespace.

Had setfoo been called, then from extra import * would have found it. But things can still be funky. Suppose some assignment sets extra.foo to 42. Well, the other module namespace doesn't know about that, so in the other module, foo would still be 5 but extra.foo would be 42.

Always keep in mind the difference between an object and the things that may be referencing the object at any given time. Objects have no idea which variables or containers happen to reference them (though they do keep a count of the number of references). If a variable or container is rebound to a different object, it doesn't change the binding of other variables or containers.

Upvotes: 0

Related Questions