views:

142

answers:

3

I want to control global variables (or globally scoped variables) in a way that they are set only once in program initialization code, and lock them after that.

I use UPPER_CASE_VARIABLES for global variables, but I want to have a sure way not to change the variable anyway.

  • Does python provide that (or similar) feature?
  • How do you control the globally scoped variables?
+1  A: 

You could wrap your global variables in an object and override the object.__setattr__ method. You can then prevent setting attributes that are already set. However, this doesn't deal with complex objects that are passed by reference. You would need to make shallow/deep copies of those objects to be absolutely sure they can't be modified. If you are using new style classes you could override object.__getattribute__(self, name) to make the copies.

class State(object):
    def __init__(self):
        pass

    def __setattr__(self, name, value):
        if name not in self.__dict__:
            self.__dict__[name] = value

** I usually don't worry so much if someone is going to try really hard to break my code. I find overriding __setattr__ is sufficient (especially if you throw an exception) to warn whoever is playing with the code that the goal for the State is to be read only. If someone still feels the need to modify the state then whatever undefined behavior they encounter isn't my fault.

GWW
And what would prevent someone from overwriting the object?
mikerobi
Good point :P. I suppose there's not much that can be done? Maybe a singleton?
GWW
Every time someone tries to emulate constants, private members, nominative type checking, etc... in Python (or other dynamic languages), he fails. When will people learn? (A singleton class can be redefined just as easy)
delnan
You are right, I guess I've never been that concerned about someone messing with my code.
GWW
+3  A: 

Python is a very open language and does not contain a final keyword. Python gives you more freedom to do things and assumes you know who things should work. Therefore, it is assumed that people using your code will know that SOME_CONSTANT should not be assigned a value at some random point in the code.

If you really do want to, you can enclose the constant inside a getter function.

def getConstant()
  return "SOME_VALUE"
unholysampler
It's still possible (and only marginally more complicated) to do `getConstant = lambda: new_value`. And since `ALL_CAPS` is the convention for should-be constants, you should just stick to that.
delnan
+4  A: 

Activestate has a recipe titled Constants in Python by Martelli for creating a const module with attributes which cannot be rebound after creation. That sounds like what you're looking for except for the upcasing -- but I suspect that could be added by making it check to see whether the attribute name was all uppercase or not.

Of course, this can be circumvented by the determined, but that's the way Python is -- and it's considered to be a "good-thing" by most of us. To make it harder though, I suggest you do not add the so-called obvious __delattr__ method because people could then just delete names and then add them back rebound to different values.

So here's what I'm taking about:

# from http://code.activestate.com/recipes/65207-constants-in-python
class _const:
    class ConstError(TypeError): pass
    class ConstCaseError(ConstError): pass

    def __setattr__(self, name, value):
        if self.__dict__.has_key(name):
            raise self.ConstError, "Can't change const.%s" % name
        if not name.isupper():
            raise self.ConstCaseError, 'const name "%s" is not all uppercase' % name
        self.__dict__[name] = value

import sys
sys.modules[__name__] = _const()

if __name__ == '__main__':
    import const

    try:
        const.Answer = 42
    except const.ConstCaseError, exc:
        print exc
    else:
        raise Exception, "Non-uppercase only const name shouldn't be allowed!"

    const.ANSWER = 42 # OK, all uppercase
    try:
        const.ANSWER = 17
    except const.ConstError, exc:
        print exc
    else:
        raise Exception, "Shouldn't be able to change const!"
martineau
Just a remark: If you import a such a "constant" into the local namespace, it's not longer "constant". The local name can easily be rebound. And after all, the module object can always be rebound.
lunaryorn
@lunaryorn: Quite true. The recipe also appears in "Python Cookbook, 2d ed" and there Martelli mentions the fact that the value being bound can still be changed if it is mutable, such as a list. To prevent that he mentions another recipe in the book that allows one to wrap mutable objects and make them read-only. This other recipe is titled "Delegating Automatically as an Alternative to Inheritance" and there's a lot of material in its Discussion section -- more than I felt would be appropriate to put in my answer -- so I didn't go there...
martineau