Python Global Constant

Bengt Richter bokr at oz.net
Thu Jul 10 23:48:12 EDT 2003


On Thu, 10 Jul 2003 02:01:47 -0400, Jp Calderone <exarkun at twistedmatrix.com> wrote:

>On Thu, Jul 10, 2003 at 04:06:55PM +1200, Greg Ewing (using news.cis.dfn.de) wrote:
>> Peter Hansen wrote:
>> >Just do what you did above, without the "const" modifier, which
>> >doesn't exist and is not really needed in Python.
>> 
>> If you *really* insist on preventing anyone from changing
>> them, it is possible, with some hackery. Here's one way
>> that works:
>> 
>> #######################################################
>> #
>> #  MyModule.py
>> #
>> 
>> _constants = ['PI', 'FortyTwo']
>> 
>> PI = 3.1415
>> FortyTwo = 42
>> 
>> import types
>> 
>> class _ConstModule(types.ModuleType):
>> 
>>   __slots__ = []
>> 
>>   def __setattr__(self, name, value):
>>     if name in self.__dict__['_constants']:
>>       raise ValueError("%s is read-only" % name)
>>     self.__dict__[name] = value
>> 
>> del types
>> import MyModule
>> MyModule.__class__ = _ConstModule
>> 
>
>    >>> import MyModule
>    >>> del MyModule._constants[:]
>    >>> MyModule.PI = 'Cherry, please'
>    >>> MyModule.PI
>    'Cherry, please'
>
>  Jp
>
Ok, this one is a little more resistant, I think (though I'm not sure how much more ;-):
====< makeconst.py >=============================================================
import os, sys
def makeconst(m):
    modulename = m.__name__
    mpath = m.__file__
    sfront = [
    'def forclosure(m):',
    '    dictinclosure = {'
    ]
    sback = [
    '    }',
    '    class %s(object):'% modulename,
    '        def __getattribute__(self, name):',
    '            if name=="__dict__": return dictinclosure.copy()', # no mods ;-)
    '            return dictinclosure[name]',
    '        def __setattr__(self,name,val):',
    '            raise TypeError, "module %s is read-only"'%modulename,
    '    return vars()["%s"]()'%modulename,
    ''
    ]
    if mpath.endswith('.pyc'): mpath = mpath[:-1]
    if not mpath.endswith('.py'):
        raise ValueError, 'Not .py or .pyc based module: %s of %r'%(modulename, mpath)
    for line in file(mpath):
        line = line.strip()
        if not line or line[0]=='#' or not line[0].isupper(): continue
        lh,rh = map(str.strip, line.split('=',1))
        sfront.append(
    '       %r:m.%s,'% (lh,lh) # get actual bindings from pre-constified module
        )
    exec '\n'.join(sfront+sback)
    constmod = vars()['forclosure'](m)
    sys.modules[modulename] = constmod
    return constmod

def importconst(name): return makeconst(__import__(name))
=================================================================================

and we'll import and "make constant" the module:

====< MyConsts.py >====================================
#######################################################
#
#  MyConsts.py
#

PI = 3.1415
FortyTwo = 42
=======================================================
 >>> import makeconst
 >>> MyConsts = makeconst.importconst('MyConsts')
 >>> MyConsts.PI
 3.1415000000000002
 >>> dir(MyConsts)
 ['FortyTwo', 'PI']
 >>> MyConsts.FortyTwo
 42
 >>> MyConsts.FortyTwo = 43
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<string>", line 11, in __setattr__
 TypeError: module MyConsts is read-only
 >>> setattr(MyConsts,'foo',123)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<string>", line 11, in __setattr__
 TypeError: module MyConsts is read-only
 >>> MyConsts.__dict__
 {'PI': 3.1415000000000002, 'FortyTwo': 42}
 >>> MyConsts.__dict__['PI'] = 'Cherry?'
 >>> MyConsts.__dict__
 {'PI': 3.1415000000000002, 'FortyTwo': 42}
 >>> vars(MyConsts)
 {'PI': 3.1415000000000002, 'FortyTwo': 42}
 >>> MyConsts.PI
 3.1415000000000002


 >>> object.__getattribute__(MyConsts,'__dict__')
 {}
 >>> object.__getattribute__(MyConsts,'__class__')
 <class 'makeconst.MyConsts'>
 >>> object.__getattribute__(MyConsts,'__class__').__dict__
 <dict-proxy object at 0x007DD6F0>
 >>> object.__getattribute__(MyConsts,'__class__').__dict__.keys()
 ['__module__', '__dict__', '__getattribute__', '__weakref__', '__setattr__', '__doc__']
 >>> object.__getattribute__(MyConsts,'PI')
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 AttributeError: 'MyConsts' object has no attribute 'PI'

Well, we can force one that will come back that way
 >>> object.__setattr__(MyConsts,'PI','Cherry ;-)')

Not this way:
 >>> MyConsts.PI
 3.1415000000000002

But this way:
 >>> object.__getattribute__(MyConsts,'PI')
 'Cherry ;-)'

We can see it in the instance dict that was created:
 >>> object.__getattribute__(MyConsts,'__dict__')
 {'PI': 'Cherry ;-)'}

But not via the object normally:
 >>> dir(MyConsts)
 ['FortyTwo', 'PI']
 >>> MyConsts.__dict__
 {'PI': 3.1415000000000002, 'FortyTwo': 42}
 >>> getattr(MyConsts,'__dict__')
 {'PI': 3.1415000000000002, 'FortyTwo': 42}

So you can force the instance to get a __dict__ with whatever apparent attribute, but you
can't make it apparent via normal attribute access, so it wouldn't affect modules importing
and using MyConsts normally:

 >>> MyConsts.PI
 3.1415000000000002

Well, some you can flesh out the special attributes/methods, but you get the drift.
We could also use a metaclass to fake the name exactly (when __name__ is returned ;-)
and in __repr__ and __str__.

If you import MyConsts after makeconst does its thing, I'm not sure how to sabotage the result of
the expression MyConsts.PI for other modules that have imported it, short of rebinding methods.
Since the data is not in a class variable or class dict, I you'd have to go after the closure cell.
I can find that (I think that's it below, though I'd have to print id(dictinclosure) to be sure),
e.g.,

 >>> object.__getattribute__(MyConsts,'__class__').__getattribute__.im_func.func_closure[0]
 <cell at 0x007D8FB0: dict object at 0x007DD5E0>

but how do you get at the dict in the cell? I don't guess it should be allowed, even if it's possible.

Otherwise you'll just have to mess with sys.modules similarly to get around it, I think, or accept
that you just have constant bindings (maybe to mutable objects though ;-)

Regards,
Bengt Richter




More information about the Python-list mailing list