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