views:

107

answers:

5

I'm building a module where there are a whole lot of diverse objects which need to be controlled by several variables.

If this were C, I would pass the objects a pointer to the variable / object I want them to watch, have the application change the variable as it needs to, and the objects in the module would "see" the new value.

In module foo.py:

class foo(object):
  def __init__(self, varToWatch):
    self.varToWatch = varToWatch
  def DoSomething(self):
    print self.varToWatch

In application, bar.py:

import foo
x = "before"
y = foo.foo(x)
y.DoSomething()
x = "after"
y.DoSomething()

This doesn't work, of course, because the original value of x (or a reference to the original value of x?) is stored in y. You get "before" "before".

So, I try...

  1. Passing the name of a global, and referring to the global by name in foo. No go, because the global is in the application's space, not in the module.
  2. Using a global in the foo namespace, but I don't know how to address that space by variable name, and we're currently doing import * from foo rather than import foo. Changing this would be painful.
  3. Make a dictionary in the module and pass the name of a key in the dictionary to the objects in the module. Manipulate the contents of the dictionary from the application. Basically, I'm defining a new global space and sticking variables in it. I see no reason it wouldn't work, but it feels untidy to me. Is that just me being unfamiliar with Python?

I'm not finding the kind of beautiful solutions I'm used to seeing come easily with Python, so I suspect I'm missing something.

Suggestions?

TIA, - Tim.

+2  A: 

How about putting the control variables in a separate module -- say, settings -- and doing a separate import settings? You could then have your objects watch settings.bar and settings.quux while still doing from foo import * to clutter up your main namespace. ;-) You could also use a global object to store your settings, with mostly the same effect.

Of course, with problems such as these, one must wonder whether restructuring things to do a import foo wouldn't help things in the long run... Impossible to judge without knowing the problem, though, so I'll leave this problem to you.

Michał Marczyk
Ok, but how do I identify the variable to the classes and the application? If I do it by name, how do I get to that variable in the settings namespace? There muyst be some obvious way, but I couldn't find it. settings.global() doesn't do it.
tbroberg
You can simply use `getattr(settings, varname)` to get a variable from any object's namespace.
Max Shawabkeh
@tbroberg: What Max says! For example `getattr(foo, "bar")` returns the value of `foo.bar`, where `foo` may be a module (or any object). You'd have to use `import foo` rather than `from foo import *` to access `foo` in this manner, though; thus a separate module for things which you may want to access in this way may make for the cleanest code. @Max: Thanks for the comment!
Michał Marczyk
@tbroberg: BTW, my second suggestion was to use a global object called, say, `foo.settings`; you could then use it in very much the same manner as a settings module. I have to say I find it cleaner and in some ways more robust to keep settings / configuration well separated from code. Just being able to edit them without touching the files containing the logic is important enough (helps with version control and general sanity, I find).
Michał Marczyk
(...continuing...) Even for things meant as globals for runtime, it's nice to have them grouped in one place so as to ease debugging etc. Although on this front an object does have some good points, the ability to take advantage of `@property` comes to mind; but you can still replace a module with an object for debugging, it's basically a different type of namespace -- a nice thing about Python, this! Finally, and this wins me over, a separate module is *the* way to go if you're planning on documenting things properly -- a separate file for all code, comments etc. related to your globals.
Michał Marczyk
Ok, so I can define a module, settings.py, define a bunch of variables in it, tweak those variables... Wait, if the application writes directly to the module variables, I will get a new reference in the application's namespace, right? So, I would have to stuff things in the dictionary of the module. Ok, semi-tidy.If I create an object (following the example of the Scapy conf object):class Settings(object): x = 'before'settings = Settings()I can now read or write settings.x directly from anywhere, and I can use getattr(settings, varname) to reference them by name.Win!Thanks, all!!!
tbroberg
You're welcome. :-) BTW, you can also use `setattr(settings, varname, value)` to set the value of `varname` from anywhere, regardless of whether `settings` happens to be a module or an object. You can even add an entry to a module's namespace in this way. Should have mentioned that earlier! (Maybe you'll convert to the module way now... ;-)) All best!
Michał Marczyk
+1  A: 

The trick is to use a mutable type. Strings are immutable, so when you change the string, it creates a new string object in a new memory space. If, however, you referenced a list and appended to the list, the memory space won't change. Adapting your code...

>>> class Foo(object):
...     def __init__(self, var):
...         self.var = var
...     def show(self):
...         print self.var
... 
>>> x = ['before']
>>> y = Foo(x)
>>> y.show()
['before']
>>> x.append('after')
>>> y.show()
['before', 'after']
robhudson
Yes, that is the best solution I came to as well (#3 in the original post). I was hoping there was a more orderly answer than wrapping them all in lists or dicts, but if there ain't then there ain't.Thanks.
tbroberg
I think it's better with:print self.var -> print self.var[0]and x.append('after') -> x[0] = 'after'
wiso
+1  A: 

Your problem here is that in C variables are data are objects. In Python, variables are always references to objects (which are data). You can easily watch an object, but not a variable. For example, x = "after" actually "unlinks" the object before from x and links a new object ("after"). This means that x and foo.varToWatch no longer refer to the same object.

If, on the other hand, you mutate the object referenced by x, both x and varToWatch still refer to the same object and the change is reflected. An example would be if x was a mutable type, like a dictionary - changing x in this case would reflect in varToWatch unless you actually do an assignment.

Max Shawabkeh
Agreed. This is kind of where I was going with the dictionary idea, and a list would work too.It just doesn't feel clean to me, but perhaps I'm still mired in my C ways.
tbroberg
Well, I have to agree that it isn't clean. My post was addressing the lower level issues. On the higher level, I'd suggest going with Michal's answer of having a `settings` or `config` module.
Max Shawabkeh
+3  A: 

To catch all re-bindings of a name, you need to pass both a namespace and a specific name to use as the key within it. Using a dedicated dict as the namespace is fine, but the dict for the whole module (which you can get as globals()) or for any specific object o of which you want to observe an attribute (use vars(o)) are just as fine.

So:

class foo(object):
  def __init__(self, namespace, name):
    self.namespaceToWatch = namespace
    self.nameToWatch = name
  def DoSomething(self):
    print self.namespaceToWatch[self.nameToWatch]

and, for your example use, since you're at global level:

import foo
x = "before"
y = foo.foo(globals(), 'x')
y.DoSomething()
x = "after"
y.DoSomething()
Alex Martelli
Thanks, good tip, especially about passing in globals().What if the parties agree to use the namespace of the module? How can the application get the module's namespace?
tbroberg
`globals()` **is** the namespace dictionary of the module where the call to `globals()` lexically resides (a shorthand for `vars(sys.modules[__name__])`, in a sense). If you're referring to "automagically finding the caller's globals", use of such introspection in production code is a bad idea -- anyway, you can find several SO questions and answers discussing this (both showing how, and explaining why it's not a good idea). Java has even fewer options in this regards (no globals!) so you may want to ask how it's done _there_ in another Q.
Alex Martelli
@Alex: Would you please explain or point us toward why using introspection in production code is bad? I've tried googling combinations of things like `site: stackoverflow.com martelli inspect python bad` but am coming up empty.
unutbu
@unutbu, the general issue is that Python's introspective features are mostly focused and tuned for debugging, not production work: indeed some crucial parts, such as the ability to get to the caller's frame, are very specifically documented as **not** being supported in all Python implementations. So by relying on them for production work you're potentially cutting yourself off from future, highly optimized implementations (e.g. I wouldn't surprised if Unladen Swallow was one such, once it's merged into Python 3.2) -- how can this possibly be justified?!
Alex Martelli
@Alex: Ok, thanks very much for the info!
unutbu
+1  A: 

In a comment to Alex Martelli's solution, tbroberg asks, "What if the parties agree to use the namespace of the module? How can the application get the module's namespace?"

Here is how you could set the namespace (by default) to be the caller's namespace:

import inspect
class foo(object):
    def __init__(self, name, namespace=None):
        self.namespaceToWatch = (namespace if namespace else
                                 inspect.currentframe().f_back.f_globals)
        self.nameToWatch = name
    def DoSomething(self):
        print self.namespaceToWatch[self.nameToWatch]

Note that I changed the order of the arguments so namespace is now an optional argument.

inspect.currentframe() returns the current frame, inside foo.__init__.

inspect.currentframe().f_back returns the previous frame, from which foo('x') is being called.

inspect.currentframe().f_back.f_globals returns the global namespace of this frame. This makes

y = test.foo('x')

equivalent to

y = test.foo('x', globals())
unutbu
Wow! It's great to see that that can be done. The level of complexity involved is a good cue that it's well off the beaten path. Thanks!
tbroberg