Behaviour of enumerated types

Bengt Richter bokr at oz.net
Sat Nov 19 21:08:19 EST 2005


On Sun, 20 Nov 2005 08:42:48 +1100 (EST), Ben Finney <bignose+hates-spam at benfinney.id.au> wrote:

>Bengt Richter <bokr at oz.net> wrote:
>> On Sat, 19 Nov 2005 11:10:42 +1100 (EST), Ben Finney <bignose+hates-spam at benfinney.id.au> wrote:
>> >Bengt Richter <bokr at oz.net> wrote:
>> >> If [an enumeration has a fixed sequence], what is more natural
>> >> than using their index values as keys to other ordered info?
>> >I don't see why. If you want sequence numbers, use enumerate(). If
>> >not, the enum object itself can be used directly as an iterable.
>>
>> I changed mine so the enum _class_ is iterable, but enum instances
>> are not.
>
>I'm not really understanding your design.
>
>In my enum package, an enumeration is an instance of Enum. Those
>instances are iterable, producing EnumValue instances; the EnumValue
>instances are also available as named attributes of the Enum instance.
I have a similar situation, except I have an enum factory function makeEnum
instead of a class Enum, and the factory returns not an instance of Enum,
but an enum as a unique type, i.e., a class. But it is functionally analogous,
except I differentiate by
>
>    >>> from enum import Enum
>    >>> colours = Enum('red', 'blue', 'green')
>    >>> for val in colours:
>    ...     print str(val)
>    ...
>    red
>    blue
>    green
>
That corresponds to

 >>> from makeenum import makeEnum
 >>> Colours = makeEnum('Colours', 'red blue green')
 >>> for val in Colours:
 ...     print str(val),'-(repr)->', repr(val)
 ...
 red -(repr)-> Colours.red
 blue -(repr)-> Colours.blue
 green -(repr)-> Colours.green

I added the repr to show that the vals were instances of something,
and they are actuall instances of the Colours type, which was uniquely
created to represent the enumeration, along with its fixed set of internally
cached immutable instances, references to which are returned by the Colours
constructor (__new__) instead of creating potentially duplicate instances.
So these instances can serve as sentinels too. The id's don't change, no
matter how many you construct (of a given member of the instance set). Of
course there are len(Colours) fixed (once created and cached) instances
of Colours in all.

 >>> list(Colours)
 [Colours.red, Colours.blue, Colours.green]
 >>> map(str, Colours)
 ['red', 'blue', 'green']
 >>> map(int, Colours)
 [0, 1, 2]
 >>> int(Colours.blue)
 1
 >>> Colours[1]
 Colours.blue


>Why would the Enum *class* be iterable?
That corresponds to asking why my makeEnum should be iterable,
and the it is not. What makeEnum makes functionally corresponds
to your Enum instance, except my "instance" is a unique manufactured class.
>
>> >> OTOH, the index values (and hence my enums) are[1] not very good
>> >> as unique dict keys, since they compare[2] promiscuously with
>> >> each other and other number types.
>> >Indeed, that's why (in my design) the values from the enum are only
>> >useful for comparing with each other. This, to me, seems to be the
>> >purpose of an enumerated type.
>>
>> Have you tried yet to use two different enum instances as keys in
>> the same dict?
>
>What do you mean by "enum instances"? I presume you mean "values from
>a single enum".
Yes, by analogy to your functionality, but since my "single enum" is
actually a class returned by makeEnum, not an instance of Enum, i.e.,

 >>> Colours
 <class 'makeenum.Colours'>

and the "values" are immutable singleton instances that you can access via
the class (which I gave some unusual capabilities -- i.e., some methods
that apply to it as an ordered collection of immutable instances, and
which aren't accessible as methods of the instances (though the instances
have special methods of their own, and inherit methods from int).

>
>    >>> colour_german = { colours.red: "rot", colours.green: "grün",
>    >>> colours.blue: "blau" }
>    >>> for val in colours:
>    ...     print colour_german[val]
>    ...
>    rot
>    blau
>    grün
That's a plain dict with colour "values" as keys. Same as (checking the order first
to get the zip correspondence right ;-)

 >>> list(Colours)
 [Colours.red, Colours.blue, Colours.green]


 >>> colour_german = dict(zip(Colours, ('rot', 'blau', 'grün')))
 >>> for val in Colours:
 ...     print 'colour_german[%r] => %s' %(val, colour_german[val])
 ...
 colour_german[Colours.red] => rot
 colour_german[Colours.blue] => blau
 colour_german[Colours.green] => grün


>
>> Then try to sort the keys(or items is the values are misc different
>> enums).
>
>Oh, perhaps you mean "enum values from different enum instances". No,
Yes, again by analogy.
>those won't compare against each other; there's no meaningful
>relationship, since different enum instances are conceptually
>different types.
Well, yes, I made that the strict default cmp, but I can optionally make
"Enum instances" (makeEnum returned classes) whose values cmp differently.
But now that I think of it, sorting is pretty controllable anyway, e.g,
suppose we had a Coins enum:

 >>> Coins = makeEnum('Coins', 'penny nickel dime quarter')
 >>> Colours[:] + Coins[:]
 [Colours.red, Colours.blue, Colours.green, Coins.penny, Coins.nickel, Coins.dime, Coins.quarter]

 >>> sorted(Colours[:] + Coins[:], key=repr)
 [Coins.dime, Coins.nickel, Coins.penny, Coins.quarter, Colours.blue, Colours.green, Colours.red]

 >>> sorted(Colours[:] + Coins[:], key=str)
 [Colours.blue, Coins.dime, Colours.green, Coins.nickel, Coins.penny, Coins.quarter, Colours.red]

 >>> map(str, sorted(Colours[:] + Coins[:], key=str))
 ['blue', 'dime', 'green', 'nickel', 'penny', 'quarter', 'red']
 >>> map(str, sorted(Colours[:] + Coins[:], key=int))
 ['red', 'penny', 'blue', 'nickel', 'green', 'dime', 'quarter']
 >>> map(str, sorted(Colours[:] + Coins[:], key=type))
 ['red', 'blue', 'green', 'penny', 'nickel', 'dime', 'quarter']
 >>> map(str, sorted(Colours[:] + Coins[:], key=lambda x:(type(x).__name__, int(x))))
 ['penny', 'nickel', 'dime', 'quarter', 'red', 'blue', 'green']
 >>> sorted(Colours[:] + Coins[:], key=lambda x:(type(x).__name__, int(x)))
 [Coins.penny, Coins.nickel, Coins.dime, Coins.quarter, Colours.red, Colours.blue, Colours.green]

 >>> sorted(Colours[:] + Coins[:], key=repr)
 [Coins.dime, Coins.nickel, Coins.penny, Coins.quarter, Colours.blue, Colours.green, Colours.red]


>
>> I hit that, and changed __cmp__ to compare (typename, <intvalue or
>> other if not int subtype>) tuples.
>
>I think this is a flaw, based on expecting too strong a relationship
>between the enum value instance, and an integer value.
Actually, I'm not "expecting" it, I am defining it that way ;-)
Note the parallels between

 >>> Bool = makeEnum('Bool', 'False True')
 >>> issubclass(Bool, int)
 True
 >>> issubclass(bool, int)
 True
 >>> isinstance(Bool.False, int)
 True
 >>> isinstance(     False, int)
 True
 >>> isinstance(Bool.True, int)
 True
 >>> isinstance(     True, int)
 True
 >>> bf = bool(0)
 >>> bf2 = bool(0)
 >>> bf is bf2
 True
 >>> Bool
 <class 'makeenum.Bool'>
 >>> Bf = Bool(0)
 >>> Bf2 = Bool(0)
 >>> Bf is Bf2
 True
 >>> Bf
 Bool.False
 >>> Bf2
 Bool.False
 >>> bf
 False
 >>> bf2
 False
 >>> map(int, [True, False])
 [1, 0]
 >>> map(int, [Bool.True, Bool.False])
 [1, 0]

>
>> That sorts items grouped by enum type if they're keys.
>
>Why should colours.blue compare before fruits.orange? How is that
>meaningful?
Like Finneys come before Richters in the telephone book ;-)

>
>> >I think I've addressed all your current concerns; I don't believe
>> >an inherent correlation to integers is necessary at all.
>> 
>> Necessary wasn't the question for me. It's whether it's desirable.
>> YMMV ;-)
>
>I'm still trying to understand what is served by having some exposed
>relationship between an enum value instance and an integer value.
The relationship is effectively __int__ giving the index position in
the originally specified sequence of names, without having to do an index
operation, and without having to do anything but use a reference to
the value in a context where the integer value is useful.

>
>> >It's no more necessary than saying that ["a", "b", "c"] requires
>> >that there be some specific correlation between the values of that
>> >list and the integers 0, 1, 2. If you *want* such a correlation, in
>> >some particular case, use enumerate() to get it; but there's
>> >nothing about the values themselves that requires that
>> >correspondence.
That's true for arbitrary values in a list, but IMO it's not true for
values whose names were originally specified in a particular order for
(presumably -- or doesn't your enum care about the order??) a reason.
Enums in other languages have that default correspondence typically,
from what I can recall, and promotion to integer in arithmetic contexts
is also common. Having different enumerations be distinct _types_ is
exactly what C++ does. And Pascal.

>>
>> Yet it is comforting to know that ['a', 'b', 'c'][0] will interpret
>> the [0] to mean the first in the sequence (unless someone is doing a
>> list-like repr trick based on some other type ;-).
>
>Again, you're only talking about *sequence*, not correspondence to
>integers. Your case above isn't an argument in favour of "the 'a'
>value should coerce to the 0 value". Why then should an enum value
>instance coerce to any particular integer value?
The particular integer values have no relation to their position in
an _arbitrary_ list, but they do have that relation to their position
in the originally specified sequence of their names.

>
>> The class methods are introduced via metaclass in the makeEnum factory
>> and it's a very hacky workaround for the fact that execution of a
>> class definition body only has local and module global namespace, so
>> you can't directly reference anything local to the factory function.
>
>Here you've lost me, since I don't understand why you want
>enumerations to be classes at all. Are you going to be making
>instances of an entire enumeration? If not, why make classes instead
>of objects?
For the same reason C++ does. Different enumerations are logically
different types, not just different objects.

Somehow ISTM maybe you are using the enum name in an extended way that
really maybe calls for another name. Some kind of homogeneous frozen list
with names for the elements, which e.g. is more like struct than enum in C++.

What are the real use cases for your enums? And how are the same problems
usually solved? I guess I see the default case for C++ as a model, and
might extend that to a sparse sequence of integer values with names, but
without allowing instances of intervening values like C++. I don't know,
I have a feeling some experiments are just featuritis. Since sorts can
easily be controlled with a key function, maybe configuring with different
__cmp__ and __eq__ etc. is overkill. What's needed is some real use cases.
Maybe there is a place for both kinds of enums, if we give them different names ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list