1

I've got a Python module which has several variables with hard-coded values which are used throughout the project. I'd like to bind the variables somehow to a function to redirect to a config file. Is this possible?

# hardcoded_values.py
foo = 'abc'
bar = 1234

# usage somewhere else in another module
from hardcoded_values import *
print foo
print bar

What I want to do is change only hardcoded_values.py, so that print foo transparently calls a function.

# hardcoded_values.py
import config
foo = SomeWrapper(config.get_value, 'foo') # or whatever you can think of to call config.get_value('foo')
...

config.get_value would be a function that is called with parameter 'foo' when using variable foo (as in print foo).

orange
  • 7,755
  • 14
  • 75
  • 139
  • I didn't get what would be in the `config` module. I would recommend to put functions on the `harcoded_values.py` file, like `def foo(): return SomeWrapper(config.get_value('foo'))`. Then, from the other module, you would do `from harcoded_values import foo` (it is not recommended to import everything with *), and then `my_var = foo()`. – tomasyany Jun 19 '15 at 01:32
  • See my updated question. This is just an example, so don't worry about *-imports, etc. – orange Jun 19 '15 at 01:35
  • Ok got it. I would do it as I said in my comment before. Instead of importing a variable into the other module (which I'm not sure you are able to do), I would import the function which returns the value. – tomasyany Jun 19 '15 at 01:37
  • Importing the variable is the status quo. That's the crux of my question. I know it could have been done differently, but that's another question... – orange Jun 19 '15 at 01:38
  • Of course you can import variables... – orange Jun 19 '15 at 01:39
  • Ok, got it now. I tried sth similar as what you proposed, and it worked. I faked the `SomeWrapper`, and even put a print on it. Everytime I called the `print(foo)` from another file, that function was called correctly, first executing a `print`, then returning the value for foo. In other words, `foo = SomeWrapper(...)` worked for me. – tomasyany Jun 19 '15 at 01:54
  • 1
    What is wrong with ```foo = a_function('foo')```? Te return value of ```a_function``` will be assigned to ```foo``` once when *imported*. – wwii Jun 19 '15 at 02:02
  • "...will be assigned to foo once when imported" - that's what's wrong with it :) I still want it to behave like a function, i.e. return the value when it's called (not during import). – orange Jun 19 '15 at 02:24
  • Are the config values dynamic? Why doesn't a single initial assignment work? – wwii Jun 19 '15 at 02:31
  • ```foo``` is just a name that *points to* the thing on the right-hand-side of the assignment. The right-hand-side is evaluated during the assignment. If the evaluation returns a callable object then you can only access its return value by calling it - ```foo()```. If you only care about this functionality for a ```print``` statement/function then mabe the object's ```__str__`` method can be adequately constructed. – wwii Jun 19 '15 at 02:40
  • @wwii Let's wait what other people dig out before ruling this out. I'm pretty sure I've seen something like this before in a library, but can't remember where. – orange Jun 19 '15 at 02:43
  • No, `print` is just an example. I'd like to find a universally working example. The problem is entirely made up to demonstrate one way where this is useful. – orange Jun 19 '15 at 02:44
  • 1
    I still don't understand why you want to do this. It sounds like a workaround for you not having very good tools to do refactoring. Code where variables magically do something different is going to trip up anyone looking at it, probably including you in a few months. If they need to be function calls, explicitly make them into function calls. – TessellatingHeckler Jun 19 '15 at 03:52

3 Answers3

2

You definitely cannot do this if the client code of your module uses from hardcoded_variables import *. That makes references to the contents of hardcoded_variables in the other module's namespace, and you can't do anything with them after that.

If the client code can be changed to just import the module (with e.g. import hardcoded_variables) and then access its attributes (with hardcoded_variables.foo) you do have a chance, but it's a bit awkward.

Python caches modules that have been imported in sys.modules (which is a dictionary). You can replace a module in that dictionary with some other object, such as an instance of a custom class, and use property objects or other descriptors to implement special behavior when you access the object's attributes.

Try making your new hardcoded_variables.py look like this (and consider renaming it, too!):

import sys

class DummyModule(object):
    def __init__(self):
        self._bar = 1233

    @property
    def foo(self):
        print("foo called!")
        return "abc"

    @property
    def bar(self):
        self._bar += 1
        return self._bar

if __name__ != "__main__":    # Note, this is the opposite of the usual boilerplate
    sys.modules[__name__] = DummyModule()
Blckknght
  • 100,903
  • 11
  • 120
  • 169
2

I'm pretty sure that you can't do what you want to do if you import like from hardcoded_values import *.

What you want to do is to set foo to some function, and then apply the property decorator (or equivalent) so that you can call foo as foo rather than foo(). You cannot apply the property decorator to modules for reasons detailed here: Why Is The property Decorator Only Defined For Classes?

Now, if you were to import hardcoded_values then I think there is a way to do what you want to hardcoded_values.foo. I have a pretty good feeling that what I am about to describe is a BAD IDEA that should never be used, but I think it is interesting.


BAD IDEA???

So say you wanted to replace a constant like os.EX_USAGE which on my system is 64 with some function, and then call it as os.EX_USAGE rather than os.EX_USAGE(). We need to be able to use the property decorator, but for that we need a type other than module.

So what can be done is to create a new type on the fly and dump in the __dict__ of a module with a type factory function that takes a module as an argument:

def module_class_factory(module):
    ModuleClass = type('ModuleClass' + module.__name__, 
                       (object,), module.__dict__)
    return ModuleClass

Now I will import os:

>>> import os
>>> os.EX_USAGE
64
>>> os.getcwd()
'/Users/Eric'

Now I will make a class OsClass, and bind the name os to an instance of this class:

>>> OsClass = module_class_factory(os)
>>> os = OsClass()
>>> os.EX_USAGE
64
>>> os.getcwd()
'/Users/Eric'

Everything still seems to work. Now define a function to replace os.EX_USAGE, noting that it will need to take a dummy self argument:

>>> def foo(self):
...     return 42
... 

...and bind the class attribute OsClass.EX_USAGE to the function:

>>> OsClass.EX_USAGE = foo
>>> os.EX_USAGE()
42

It works when called by the os object! Now just apply the property decorator:

>>> OsClass.EX_USAGE = property(OsClass.EX_USAGE)
>>> os.EX_USAGE
42

now the constant defined in the module has been transparently replaced by a function call.

Community
  • 1
  • 1
Eric Appelt
  • 2,843
  • 15
  • 20
0

If I understand correctly, you want your hardcoded_variables module evaluated every time you try to access a variable.

I would probably have hardcoded_variables in a document (e.g. json?) and a custom wrapper function like:

import json
def getSettings(var):
    with open('path/to/variables.json') as infl:
        data = json.load(infl)
    return infl[var]
Geotob
  • 2,847
  • 1
  • 16
  • 26
  • The problem is that the variable is used throughout the application. I'd like to make one change to the variable and not many changes to every place where it's being used. – orange Jun 19 '15 at 03:32