Immutable instances, constant values

Bengt Richter bokr at oz.net
Sat Nov 19 09:14:26 EST 2005


On Sat, 19 Nov 2005 21:45:40 +1100, Steven D'Aprano <steve at REMOVETHIScyber.com.au> wrote:

>On Fri, 18 Nov 2005 14:32:46 +1100, Ben Finney wrote:
>
>> 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?
>
>That's precisely how I understand "constant" to be useful. Consider:
>
>c = 2.9979246e+08 # speed of light in metres per second
>
>def energy(mass):
>    """Converts mass in kilograms to energy in joules"""
>    return mass*c**2
>
>That works fine until the name c is rebound to some other value. The fact
>that the float object 2.9979246e+08 is immutable is necessary but not
>sufficient.
>
>
>> 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?
>
>I can't see why it would be bad style. That's what FrozenSet does.
>
>> In the case of 'enum', can anyone argue for or against an enumerated
>> type being immutable? Its values being constant?
>
>According to Gary Larson, there are four fundamental types of people. You
>can tell them apart from their reaction to being given a half-filled glass:
>
>"The glass is half full."
>
>"The glass is half empty."
>
>"Half full. No, half empty! No, half full. Wait... what was the question?"
>
>"Hey! I ordered a cheeseburger!"
>
>We might handle this with:
>
>states = Enum("full", "empty", "indeterminate", "cheeseburger")
>
>which then get referred to with:
>
>states.full
>states.empty
>states.indeterminate
>states.cheeseburger
>
>I might decide that "argumentative" is a better label than "cheeseburger",
>and mutate the appropriate enum value. What should happen? I see three
>possibilities:
>
>(1) states.cheeseburger still hangs around, but is not equivalent to
>states.argumentative, in much the same way that reloading a module can
>leave obsolete objects in your namespace (reloading doesn't automatically
>cause names that point to objects from a module to rebind to new objects).
>
>(2) references to states.cheeseburger raise an exception
>
>(3) references to states.cheeseburger are equivalent to
>states.argumentative. A rose by any other name. It shouldn't matter what
>label we put on the enums, in principle.
>
>Possibility (1) is obviously Bad with a Capital B, and should be avoided.
>
>Possibility (2) has the saving grace that it is no different to what
>happens if you rebind class attributes. There is an argument that if you
>can rebind class attributes, you should be able to rebind enums and face
>the consequences (good bad or indifferent) as best you can.
>
>Possibility (3) is the logical solution. It shouldn't matter what labels
>we put on the enums. But I fear not practical unless the enums are
>implemented as Singletons (multitons?), and perhaps not even then. 
>
>Alternatively, you could bypass the whole problem by making enums
>immutable.
>
as in

 >>> from makeenum import makeEnum
 >>> states = makeEnum('states', 'full empty indeterminate cheeseburger')
 >>> for state in states: print repr(state)
 ...
 states.full
 states.empty
 states.indeterminate
 states.cheeseburger
 >>> for state in states: print state
 ...
 full
 empty
 indeterminate
 cheeseburger
 >>> states
 <class 'makeenum.states'>
 >>> states[1]
 states.empty
 >>> states['empty']
 states.empty
 >>> states[states.empty]
 states.empty
 >>> list(states)
 [states.full, states.empty, states.indeterminate, states.cheeseburger]
 >>> map(str, states)
 ['full', 'empty', 'indeterminate', 'cheeseburger']
 >>> len(states)
 4
 >>> states.full in states
 True
 >>> int(states.full)
 0
 >>> map(int, states)
 [0, 1, 2, 3]
 >>> 'ABC'[states.empty]
 'B'
 >>> states.empty == 1
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "makeenum.py", line 80, in __cmp__
     assert type(self)==type(other), (
 AssertionError: incompatible cmp types: states vs int

We can optionally make cmp work on int(self), int(other) for the enum...
 >>> states = makeEnum('states', 'full empty indeterminate cheeseburger', strict=int)
 >>> 'ABC'[states.empty]
 'B'
 >>> states.empty == 1
 True
 >>> type(states.empty)
 <class 'makeenum.states'>
 >>> a = list(states)
 >>> b = list(states)
 >>> [x is y for x,y in zip(a,b)]
 [True, True, True, True]
 >>> [x is y for x,y in zip(a,states)]
 [True, True, True, True]

(all the states are effectively singleton instances of the states class)
and immutable:

 >>> states.full = 'topped_off'
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "makeenum.py", line 34, in __setattr__
     raise AttributeError, '%s attributes may not be modified'%cls.__name__
 AttributeError: states attributes may not be modified
 >>> states.full.foo = 'trick'
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "makeenum.py", line 103, in __setattr__
     raise AttributeError, '%s instance attributes may not be set'% type(self).__name__
 AttributeError: states instance attributes may not be set
 >>> states[0] = 'something'
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: object does not support item assignment

But a slice returns a list, so the transient list absorbs the assignment and disappears
 >>> states[:][0] = 'something'
 >>> states[:]
 [states.full, states.empty, states.indeterminate, states.cheeseburger]
 >>> states[:]+['something']
 [states.full, states.empty, states.indeterminate, states.cheeseburger, 'something']
 >>> Fruit = makeEnum('Fruit', 'apple banana')
 >>> Fruit[0]
 Fruit.apple
contains demands type equality
 >>> Fruit[0] in states
 False
 >>> states[0] in Fruit
 False
 >>> int(Fruit[0])
 0
 >>> int(states[0])
 0

Fruit was created strict, so
 >>> Fruit[0] == states[0]
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "makeenum.py", line 80, in __cmp__
     assert type(self)==type(other), (
 AssertionError: incompatible cmp types: Fruit vs states
i.e., Fruit was created with default strict cmp requiring equal types, but
the last states was done with int casting for comparison, so

 >>> states[0] == Fruit[0]
 True

This thing is evolving, adding some features from Ben Finney's PyPI enum ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list