Immutable instances, constant values
Bengt Richter
bokr at oz.net
Fri Nov 18 05:40:25 EST 2005
On Fri, 18 Nov 2005 14:32:46 +1100 (EST), Ben Finney <bignose+hates-spam at benfinney.id.au> wrote:
>Howdy all,
>
>I've recently packaged 'enum' in PyPI. In its description, I make the
>claim that it creates "immutable" enumeration objects, and that the
>enumeration values are "constant" values.
>
>This raises questions.
>
>Is there any difference between a Python immutable value, and a
>constant? I suppose "constant" also implies that the *name* binds
>unchangeably to a particular value. Is that meaningful?
>
>How does one actually ensure an object is immutable? Is it a matter of
>overriding a bunch of methods, or is ther a neater way?
>
>Is it bad style to make a user-defined class that creates immutable
>objects? Why?
>
>In the case of 'enum', can anyone argue for or against an enumerated
>type being immutable? Its values being constant?
>
My notion of enum comes from (what I remember of) Pascal, which is
basically an ordered set of names of integers forming a type, and
ord(one_of_the_names) gets you the index value. Python's ord seems
to demand a string of length one, and doesn't seem to attempt coercion,
so that might not fly without a mod.
But what we have is named integers, much as True and False are built in
names for integer subtypes with value 1 and 0. So I'd say enums should
also be int subtypes...
Anyway, I would hope that the name->ord(name) mapping would be immutable
once defined (though not necessarily obsessively preventing the ususal end runs).
Hm, might as well be more concrete ...
>>> def makeEnum(ename, names):
... names = names.split()
... top = len(names)
... # define method functions outside class so they'll be closures accessing nested names
... def __new__(cls, name=names[0]):
... try:
... i = names.index(name)
... return int.__new__(cls, i)
... except ValueError:
... if isinstance(name, int) and 0< name < top: return int.__new__(cls, name)
... raise ValueError, 'illegal %s enum value %r'%(cls.__name__, name)
... def __repr__(self): return '%s(%s)' %(self.__class__.__name__, names[self])
... # return names with names attribute of class or instance
... class getnames(object):
... def __set__(*ignore): raise AttributeError, 'names protected'
... getnames.__get__ = lambda *ignore: names[:]
... return type(ename, (int,),{'__new__':__new__, '__repr__':__repr__, '__str__':__repr__,
... 'names':getnames()})
...
...
>>> Colors = makeEnum('Color', 'red green blue')
>>> Colors
<class '__main__.Color'>
>>> Colors()
Color(red)
>>> Colors(1)
Color(green)
>>> r,g,b = (Colors(name) for name in Colors.names)
>>> r
Color(red)
>>> g
Color(green)
>>> b
Color(blue)
>>> 'ABC'[g]
'B'
>>> int(g)
1
>>> Colors(5)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 11, in __new__
ValueError: illegal Color enum value 5
>>> Colors('blue')
Color(blue)
>>> Colors(2)
Color(blue)
>>> Colors('orange')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 11, in __new__
ValueError: illegal Color enum value 'orange'
Just some thoughts.
Oh, names are kind of protected by [:], but really should throw
an exception if you try to mutate.
>>> Colors.names
['red', 'green', 'blue']
>>> Colors.names[2]
'blue'
>>> Colors.names[2] = 'indigo'
>>> Colors.names[2]
'blue'
It would be easy to return a tuple.
It's not that easy to protect against Colors.names = something
since __metaclass__ skips factory local to global if not in class scope,
and passing a '__metaclass__': mcdefinition in the dict arg to type does
not result in a call, it just becomes another passive class variable.
Must be a way though. Too tired for now...
Regards,
Bengt Richter
More information about the Python-list
mailing list