2

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)
  • Is there any reason you're not setting `foo` while initializing `extra.py`? Otherwise a possible solution would be to set `foo = None`. – Francisco Oct 20 '16 at 03:46
  • Well like I said, this is an oversimplified version of what I'm actually trying to do. The real version of the function sets multiple variables in the way that this one sets `foo`, and I have no way of knowing which variables it's going to set. So that's out the window. – Grady Shoemaker Oct 20 '16 at 03:48

3 Answers3

1

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?

Community
  • 1
  • 1
Sanket Sudake
  • 763
  • 6
  • 18
  • "Trying to have a little fun with Python" would be accurate. See my edit above–there's no way any of that stuff could remotely be considered "pythonic". Gimme a sec to try to process what you wrote above. – Grady Shoemaker Oct 20 '16 at 04:23
  • I'm not really going to try to comprehend what `globals()` does right now (I've never even heard of that function before), but it seems as if you've got it backwards. I can and want to modify `extra.py`, it's `main.py` that I don't want to touch. – Grady Shoemaker Oct 20 '16 at 04:37
  • Check update. You dont need to modify main.py in that case. – Sanket Sudake Oct 20 '16 at 06:06
  • Didn't see your comment at first. I tried it, and the same issue is still happening. I would need to run `from extra import *` again after running the `use()` function to access the variables. But thanks for telling me about the `setattr()` function, that gets rid of that disgusting `exec` statement. – Grady Shoemaker Nov 12 '16 at 04:23
  • Is there something in the `sys.modules` that allows you to access the file that imported the code that is currently being run? – Grady Shoemaker Nov 12 '16 at 04:26
  • Wait, I'm an idiot, it did work. Nevermind then. Thanks for the help. – Grady Shoemaker Nov 12 '16 at 04:28
  • Lastly, I'm assuming the name `_mod` doesn't have any significance, just an arbitrary variable name... right? Or does it have a specific purpose? – Grady Shoemaker Nov 12 '16 at 04:30
  • No. It is just variable name. No specific purpose here to start name with `_`. I sometimes tend to us `_` for temporary/private variables in function. – Sanket Sudake Nov 12 '16 at 18:10
  • Can you please mark answer correct if it helps you ? – Sanket Sudake Nov 16 '16 at 13:54
  • Oh yeah sure, didn't realize. New to this site. – Grady Shoemaker Nov 16 '16 at 22:40
0

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.

tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • Yeah, that's what I learned from a bit of Googling around. Do you know if there's any way that `extra.py` can set variables in the namespace of whatever file imported it (`main.py` in this case)? I think that would solve my problem. – Grady Shoemaker Oct 20 '16 at 04:00
  • You can't do that but you can put `extra` inside its own container (e.g., `my_config = { 'foo':5 }`. However, a better solution is to not do `from extra import *`. Had you just done `import extra` and referenced the variable as `extra.foo` everywhere, everyone would be referencing the same object in the `extra` namespace. That is, modules are containers so just keep referencing them. – tdelaney Oct 20 '16 at 04:03
  • I don't think that's an option, sadly. It's pretty much a requirement that I access it as `foo` and not `extra.foo`. Putting it in a container like a dictionary also wouldn't accomplish what I am trying to do. – Grady Shoemaker Oct 20 '16 at 04:12
  • I once had a client who demanded that programs I delivered to him for Windows not have a file extension like ".exe". But Windows requires extensions on runnable files. I had to drop him as a client. I don't know enough about your requirements but on the face of it, it seems equally idiotic. The language doesn't work that way. – tdelaney Oct 20 '16 at 04:59
  • The whole thing is purposely idiotic, it's a way for me to write blocks of code in one line that I really should be writing in multiple lines, haha. – Grady Shoemaker Oct 20 '16 at 05:59
0

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
>>> 
Anil_M
  • 10,893
  • 6
  • 47
  • 74
  • Hate to say it but modifying the code in `main.py` is pretty much out of the question, unless I'm only modifying the way it is imported. This is actually for a really weird syntax "hack" I'm working on, so it has very specific needs and I can't use something like `foo = setfoo()` since I need that line of code to be an expression, not a statement. – Grady Shoemaker Oct 20 '16 at 04:10
  • if you can modify `extra,py` a little, pls see modified solution above. – Anil_M Oct 20 '16 at 04:29
  • This seems to be taking a bit to far, and if I were to do this, my code wouldn't work if I were to send it to someone else. Just to make it clear, I _can_ modify `extra.py` and that was the main aim of my question, it's `main.py` that I don't want to have to change to get this code to work. – Grady Shoemaker Oct 20 '16 at 04:34
  • you are not changing `main.py` with new solution, just `extra.py` – Anil_M Oct 20 '16 at 14:23