Peter said:
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 ?
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File said:
>>> 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__')
>>> object.__getattribute__(MyConsts,'__class__').__dict__
>>> 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
Not this way: 3.1415000000000002
But this way: 'Cherry ;-)'
We can see it in the instance dict that was created: {'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:
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