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