473,398 Members | 2,404 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,398 software developers and data experts.

Immutable instances, constant values

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?

--
\ "The best ad-libs are rehearsed." -- Graham Kennedy |
`\ |
_o__) |
Ben Finney
Nov 22 '05 #1
22 2090
On Fri, 18 Nov 2005 14:32:46 +1100 (EST), Ben Finney <bi****************@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
Nov 22 '05 #2
On Fri, 18 Nov 2005 14:32:46 +1100 (EST), Ben Finney <bi****************@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
Nov 22 '05 #3
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
I've recently packaged 'enum' in PyPI. [...]
My notion of enum comes from (what I remember of) Pascal


You might want to investigate the 'enum' package for my idea of how an
enumerated type can work.
which is basically an ordered set of names of integers forming a
type, and ord(one_of_the_names) gets you the index value.
Getting a numeric index might be useful in a language such as Pascal,
with no built-in dict or sequence types. In Python, where any
immutable object can be a dict key, and any sequence can be iterated,
it seems of no use.
But what we have is named integers, much as True and False are built
in names for integer subtypes with value 1 and 0.
That's an implementation detail; surely code shouldn't be expecting
any particular relationship between integers and boolean values?
So I'd say enums should also be int subtypes...


Likewise, that seems best left as an implementation detail. Why expect
any particular mapping to int values? Doing arithmetic or boolean
logic on enumerated values seems against their purpose.

--
\ "We tend to scoff at the beliefs of the ancients. But we can't |
`\ scoff at them personally, to their faces, and this is what |
_o__) annoys me." -- Jack Handey |
Ben Finney
Nov 22 '05 #4
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
I've recently packaged 'enum' in PyPI. [...]
My notion of enum comes from (what I remember of) Pascal


You might want to investigate the 'enum' package for my idea of how an
enumerated type can work.
which is basically an ordered set of names of integers forming a
type, and ord(one_of_the_names) gets you the index value.
Getting a numeric index might be useful in a language such as Pascal,
with no built-in dict or sequence types. In Python, where any
immutable object can be a dict key, and any sequence can be iterated,
it seems of no use.
But what we have is named integers, much as True and False are built
in names for integer subtypes with value 1 and 0.
That's an implementation detail; surely code shouldn't be expecting
any particular relationship between integers and boolean values?
So I'd say enums should also be int subtypes...


Likewise, that seems best left as an implementation detail. Why expect
any particular mapping to int values? Doing arithmetic or boolean
logic on enumerated values seems against their purpose.

--
\ "We tend to scoff at the beliefs of the ancients. But we can't |
`\ scoff at them personally, to their faces, and this is what |
_o__) annoys me." -- Jack Handey |
Ben Finney
Nov 22 '05 #5
On Fri, 18 Nov 2005 23:43:10 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
>I've recently packaged 'enum' in PyPI. [...]
My notion of enum comes from (what I remember of) Pascal


You might want to investigate the 'enum' package for my idea of how an
enumerated type can work.

I guess I saw an earlier version, and got confused as to the goal, sorry.
I will look in PyPI.
which is basically an ordered set of names of integers forming a
type, and ord(one_of_the_names) gets you the index value.
Getting a numeric index might be useful in a language such as Pascal,
with no built-in dict or sequence types. In Python, where any
immutable object can be a dict key, and any sequence can be iterated,
it seems of no use.

Does your concept of enumeration not have a fixed order of a set of names?
If it does, what is more natural than using their index values as keys
to other ordered info? 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. Hm, maybe if hash were defined class-unique, e.g.,
def __hash__(self): return hash((int(self), type(self).__name__)
Ok, did that, _seems_ to work (fixed __repr__ too):
[1] were & [2] compared ;-)
from makeenum import makeEnum
Few = makeEnum('Few','zero one two three')
Few() Few('zero') d = dict((Few(n), Few.names.index(n)) for n in Few.names)
d {Few('zero'): 0, Few('three'): 3, Few('one'): 1, Few('two'): 2} d[1] Traceback (most recent call last):
File "<stdin>", line 1, in ?
KeyError: 1 d[Few(1)] 1

But still can work as integer: 'ABC'[Few(1)] 'B' 'ABC'[Few('one')] 'B' 'ABC'[Few('two')] 'C'
But what we have is named integers, much as True and False are built
in names for integer subtypes with value 1 and 0.
That's an implementation detail; surely code shouldn't be expecting
any particular relationship between integers and boolean values?

Au contraire, much code depends on it, e.g.,
def verboselen(s): return '%r has %d element%s'%(s, len(s), ('s','')[len(s)==1]) ... verboselen(range(3)) '[0, 1, 2] has 3 elements' verboselen(range(0)) '[] has 0 elements' verboselen(range(1)) '[0] has 1 element'
type(len(range(3))==1) <type 'bool'> type(len(range(3))==1).mro() [<type 'bool'>, <type 'int'>, <type 'object'>] int (len(range(3))==1) 0 int (len(range(1))==1) 1
So I'd say enums should also be int subtypes...


Likewise, that seems best left as an implementation detail. Why expect
any particular mapping to int values? Doing arithmetic or boolean
logic on enumerated values seems against their purpose.

I guess I will have to look at your enum in PyPI to understand what
you mean by "their purpose" ;-)

To me the int correspondence is as expectable and natural as a,b,c=range(3)
(at least as a default) though I think different enumerations should be
different types. Note that the ordering of int values makes the instances
nicely sortable too, e.g.,
d.items() [(Few('zero'), 0), (Few('three'), 3), (Few('one'), 1), (Few('two'), 2)] sorted(d.items()) [(Few('zero'), 0), (Few('one'), 1), (Few('two'), 2), (Few('three'), 3)]

or more directly
d.keys() [Few('zero'), Few('three'), Few('one'), Few('two')] sorted(d.keys())

[Few('zero'), Few('one'), Few('two'), Few('three')]

Enumerations defined as monotonic but non-contiguous sequences of named int
values are conceivable too. They can be useful in defining bit masks with
distinguishable types, but that act like ints. Kind of a sparse enumeration.
Maybe I'll add that in.

But bottom line, I really thing the int base type is more than an implementation
detail. I think it's natural for an _ordered_ set of names ;-)

I'll go look at PyPI now ;-)

Regards,
Bengt Richter
Nov 22 '05 #6
On Fri, 18 Nov 2005 23:43:10 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
>I've recently packaged 'enum' in PyPI. [...]
My notion of enum comes from (what I remember of) Pascal


You might want to investigate the 'enum' package for my idea of how an
enumerated type can work.

I guess I saw an earlier version, and got confused as to the goal, sorry.
I will look in PyPI.
which is basically an ordered set of names of integers forming a
type, and ord(one_of_the_names) gets you the index value.
Getting a numeric index might be useful in a language such as Pascal,
with no built-in dict or sequence types. In Python, where any
immutable object can be a dict key, and any sequence can be iterated,
it seems of no use.

Does your concept of enumeration not have a fixed order of a set of names?
If it does, what is more natural than using their index values as keys
to other ordered info? 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. Hm, maybe if hash were defined class-unique, e.g.,
def __hash__(self): return hash((int(self), type(self).__name__)
Ok, did that, _seems_ to work (fixed __repr__ too):
[1] were & [2] compared ;-)
from makeenum import makeEnum
Few = makeEnum('Few','zero one two three')
Few() Few('zero') d = dict((Few(n), Few.names.index(n)) for n in Few.names)
d {Few('zero'): 0, Few('three'): 3, Few('one'): 1, Few('two'): 2} d[1] Traceback (most recent call last):
File "<stdin>", line 1, in ?
KeyError: 1 d[Few(1)] 1

But still can work as integer: 'ABC'[Few(1)] 'B' 'ABC'[Few('one')] 'B' 'ABC'[Few('two')] 'C'
But what we have is named integers, much as True and False are built
in names for integer subtypes with value 1 and 0.
That's an implementation detail; surely code shouldn't be expecting
any particular relationship between integers and boolean values?

Au contraire, much code depends on it, e.g.,
def verboselen(s): return '%r has %d element%s'%(s, len(s), ('s','')[len(s)==1]) ... verboselen(range(3)) '[0, 1, 2] has 3 elements' verboselen(range(0)) '[] has 0 elements' verboselen(range(1)) '[0] has 1 element'
type(len(range(3))==1) <type 'bool'> type(len(range(3))==1).mro() [<type 'bool'>, <type 'int'>, <type 'object'>] int (len(range(3))==1) 0 int (len(range(1))==1) 1
So I'd say enums should also be int subtypes...


Likewise, that seems best left as an implementation detail. Why expect
any particular mapping to int values? Doing arithmetic or boolean
logic on enumerated values seems against their purpose.

I guess I will have to look at your enum in PyPI to understand what
you mean by "their purpose" ;-)

To me the int correspondence is as expectable and natural as a,b,c=range(3)
(at least as a default) though I think different enumerations should be
different types. Note that the ordering of int values makes the instances
nicely sortable too, e.g.,
d.items() [(Few('zero'), 0), (Few('three'), 3), (Few('one'), 1), (Few('two'), 2)] sorted(d.items()) [(Few('zero'), 0), (Few('one'), 1), (Few('two'), 2), (Few('three'), 3)]

or more directly
d.keys() [Few('zero'), Few('three'), Few('one'), Few('two')] sorted(d.keys())

[Few('zero'), Few('one'), Few('two'), Few('three')]

Enumerations defined as monotonic but non-contiguous sequences of named int
values are conceivable too. They can be useful in defining bit masks with
distinguishable types, but that act like ints. Kind of a sparse enumeration.
Maybe I'll add that in.

But bottom line, I really thing the int base type is more than an implementation
detail. I think it's natural for an _ordered_ set of names ;-)

I'll go look at PyPI now ;-)

Regards,
Bengt Richter
Nov 22 '05 #7
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
Getting a numeric index might be useful in a language such as
Pascal, with no built-in dict or sequence types. In Python, where
any immutable object can be a dict key, and any sequence can be
iterated, it seems of no use.
Does your concept of enumeration not have a fixed order of a set of
names?


It does. The values are iterable in the same order they were specified
when creating the Enum.
If it does, 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.
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.
To me the int correspondence is as expectable and natural as
a,b,c=range(3) (at least as a default) though I think different
enumerations should be different types.
That's a complete contradiction, then. If you want them to be
different types, you don't want them to be integers.
Note that the ordering of int values makes the instances nicely
sortable too, e.g.,
That only means the enum values need to compare in the same sequence;
it doesn't mean they need to correspond to integer values.
But bottom line, I really thing the int base type is more than an
implementation detail. I think it's natural for an _ordered_ set of
names ;-)
I think I've addressed all your current concerns; I don't believe an
inherent correlation to integers is necessary at all.

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.
I'll go look at PyPI now ;-)


Feedback appreciated :-)

--
\ "Oh, I realize it's a penny here and a penny there, but look at |
`\ me: I've worked myself up from nothing to a state of extreme |
_o__) poverty." -- Groucho Marx |
Ben Finney
Nov 22 '05 #8
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
Getting a numeric index might be useful in a language such as
Pascal, with no built-in dict or sequence types. In Python, where
any immutable object can be a dict key, and any sequence can be
iterated, it seems of no use.
Does your concept of enumeration not have a fixed order of a set of
names?


It does. The values are iterable in the same order they were specified
when creating the Enum.
If it does, 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.
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.
To me the int correspondence is as expectable and natural as
a,b,c=range(3) (at least as a default) though I think different
enumerations should be different types.
That's a complete contradiction, then. If you want them to be
different types, you don't want them to be integers.
Note that the ordering of int values makes the instances nicely
sortable too, e.g.,
That only means the enum values need to compare in the same sequence;
it doesn't mean they need to correspond to integer values.
But bottom line, I really thing the int base type is more than an
implementation detail. I think it's natural for an _ordered_ set of
names ;-)
I think I've addressed all your current concerns; I don't believe an
inherent correlation to integers is necessary at all.

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.
I'll go look at PyPI now ;-)


Feedback appreciated :-)

--
\ "Oh, I realize it's a penny here and a penny there, but look at |
`\ me: I've worked myself up from nothing to a state of extreme |
_o__) poverty." -- Groucho Marx |
Ben Finney
Nov 22 '05 #9
On Sat, 19 Nov 2005 11:10:42 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
>Getting a numeric index might be useful in a language such as
>Pascal, with no built-in dict or sequence types. In Python, where
>any immutable object can be a dict key, and any sequence can be
>iterated, it seems of no use.
Does your concept of enumeration not have a fixed order of a set of
names?


It does. The values are iterable in the same order they were specified
when creating the Enum.
If it does, 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.
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? Then try to sort the keys(or items is the values are
misc different enums). I hit that, and changed __cmp__ to compare
(typename, <intvalue or other if not int subtype>) tuples. That sorts
items grouped by enum type if they're keys. I think you can always
pass a stricter cmp to sorted if you want to assert type equality.
To me the int correspondence is as expectable and natural as
a,b,c=range(3) (at least as a default) though I think different
enumerations should be different types.
That's a complete contradiction, then. If you want them to be
different types, you don't want them to be integers.

No, it's not a contradiction. Different int _sub_types are different
types ;-)
Note that the ordering of int values makes the instances nicely
sortable too, e.g.,
That only means the enum values need to compare in the same sequence;
it doesn't mean they need to correspond to integer values.

True.
But bottom line, I really thing the int base type is more than an
implementation detail. I think it's natural for an _ordered_ set of
names ;-)
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 ;-)

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. 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 ;-).

I haven't yet converted my generated enum classes to singletons,
but I did make it so you can define a named enumeration class and
iterate the class itself (its metaclass defines __getitem__). What
you get is the particular enum class instance, e.g. (... time passes,
never mind, I cached instanced for an effective singleton set of named numbers.

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.
This of course goes for the methods of the main class being constructed too,
so they are sneaked in via the metaclass before instantiating the class ;-/
(Anyone have an idea how to do this cleanly and have the same functionality
--being able to iterate the _class_ object etc.?

Anyway, this is my "feedback" ;-) What's missing in this?

----< makeenum.py >----------------------------------------------------------
def makeEnum(ename, names):
"""
Factory function to returns enumeration class with name ename,
and enumerating space-delimited names in names string.
The class is an int subtype, and instances have int values as
well as corresponding names. Different enum instances with the
same int value are distinct as dict keys, but equal used as ints,
though they are not directly comparable unless the same type.

The return class itself defines an iterator that will return
all possible instances in order. The class names property returns
a tuple of the names, and the len(TheClass) returns the number of
names or of the iteration sequence.
"""
global __global_for_mc_hack__
class __global_for_mc_hack__(type):
"""
metaclass to define methods on the enum class per se, and to pass
through closure-dependent methods (which see names & top) to the
returned class, as well as being target itself for closure-dependent
methods of the class (which can't be defined in the class body
since names in a class body are either local or module global).
"""
def __new__(mcls, cname, cbases, cdict):
cdict.update(mcls.edict)
return type.__new__(mcls, cname, cbases, cdict)
def __getattr__(cls, name):
"""make instances accessible by attribute name"""
if isinstance(name, basestring):
return cls(name) # make/retrieve-from-cache an instance
raise IndexError, '%r not a name in "%s"'%(i, cls.__name__)

# the closure cell variables
names = names.split()
top = len(names)
cache = {}

# define method functions outside class so they'll be closures accessing nested names
def __contains__(cls, other): return type(other)==cls and 0<=int(other)<top
def __getitem__(cls, i):
"""make class iterable and indexable, returning fresh instances with given values"""
if isinstance(i, basestring) and i in names or isinstance(i, (int, long)) and (0<=i<top):
return cls(i) # make an instance
raise IndexError, '%r out of range for "%s"'%(i, cls.__name__)
# stuff closure-type method functions into global metaclass to define methods
# of the enum class per se
__global_for_mc_hack__.__contains__ = __contains__
__global_for_mc_hack__.__len__ = lambda cls: len(names)
__global_for_mc_hack__.names = property(lambda cls: tuple(names))
__global_for_mc_hack__.__getitem__ = __getitem__

def __new__(cls, name=names[0]):
try: return cache[name]
except KeyError:
try:
i = names.index(name)
e = int.__new__(cls, i)
cache[name] = cache[i] = e
return e
except ValueError:
if isinstance(name, int) and 0<= name < top:
e = int.__new__(cls, name)
cache[name] = cache[names[name]] = e
return e
raise ValueError, 'illegal %s enum value %r'%(cls.__name__, name)
def __repr__(self): return '%s(%r)' %(self.__class__.__name__, names[self])

# pass closure-type method functions to global metaclass. Ick ;-/
__global_for_mc_hack__.edict = dict(
__new__ = __new__, __repr__=__repr__, __str__=__repr__)

class enum(int):
__metaclass__ = __global_for_mc_hack__
def __cmp__(self, other):
if isinstance(other, int): oval = int(other)
else: oval = other
# allow sort by type names on incompatible types XXX make optional??
return cmp( (type(self).__name__, int(self)),
(type(other).__name__, oval))

#assert type(self)==type(other), (
# 'incompatible cmp types: %s vs %s'%(type(self).__name__, type(other).__name__))
#return cmp(int(self), int(other))
def __hash__(self): return hash((int(self), type(self).__name__))
enum.__name__ = ename
del __global_for_mc_hack__
return enum
-----------------------------------------------------------------------------
I'll go look at PyPI now ;-)


Feedback appreciated :-)

The above in use looks like:
from makeenum import makeEnum
Count = makeEnum('Count', 'eeny meeny miny moe')
Count.names ('eeny', 'meeny', 'miny', 'moe') Count[1] Count('meeny') d = dict((c, int(c)) for c in Count)
d {Count('moe'): 3, Count('miny'): 2, Count('meeny'): 1, Count('eeny'): 0} Fruit = makeEnum('Fruit', 'apple banana peach pear')
d.update((c, int(c)) for c in Fruit)
d {Count('eeny'): 0, Fruit('peach'): 2, Count('moe'): 3, Fruit('apple'): 0, Count('miny'): 2, Fru
t('banana'): 1, Fruit('pear'): 3, Count('meeny'): 1} for it in sorted(d.items()): print '%20s: %r'%it ...
Count('eeny'): 0
Count('meeny'): 1
Count('miny'): 2
Count('moe'): 3
Fruit('apple'): 0
Fruit('banana'): 1
Fruit('peach'): 2
Fruit('pear'): 3
Fruit('pear') in Count False Fruit('pear') in Fruit True Fruit('plum') in Fruit Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "makeenum.py", line 65, in __new__
raise ValueError, 'illegal %s enum value %r'%(cls.__name__, name)
ValueError: illegal Fruit enum value 'plum' d[Fruit('pear')] 3 d[3] Traceback (most recent call last):
File "<stdin>", line 1, in ?
KeyError: 3 d[Fruit(3)] 3 d[Fruit('pear')] = 'juicy'
d[Fruit['pear']] 'juicy' lf = list(Fruit)
lf [Fruit('apple'), Fruit('banana'), Fruit('peach'), Fruit('pear')] lf2 = list(Fruit)
map(id, lf) [49320972, 49321068, 49321132, 49321164] map(id, lf2) [49320972, 49321068, 49321132, 49321164] len(Fruit) 4 Fruit <class 'makeenum.Fruit'> Fruit() Fruit('apple') Fruit(0) Fruit('apple') Fruit('apple') Fruit('apple') type(Fruit('apple')) <class 'makeenum.Fruit'> id(Fruit('apple')), id(lf[0]) (49320972, 49320972)

Almost forgot, I added attribute style access:
Fruit.pear Fruit('pear') d[Fruit.pear] 'juicy' d[Count(3)] 3 Count[3] Count('moe') d[Count.moe] 3 d[Fruit.pear] += ', very'
d[Fruit.pear] 'juicy, very'

Note, int(Fruit.pear) 3 d[3] Traceback (most recent call last):
File "<stdin>", line 1, in ?
KeyError: 3

isinstance(Fruit.pear, int) True type(Fruit.pear) <class 'makeenum.Fruit'> type(Fruit.pear).mro() [<class 'makeenum.Fruit'>, <type 'int'>, <type 'object'>]

But Fruit itself is derived, which is how the tricky methods work type(Fruit) <class 'makeenum.__global_for_mc_hack__'> type(Fruit).mro(type(Fruit))

[<class 'makeenum.__global_for_mc_hack__'>, <type 'type'>, <type 'object'>]

I guess an option could be passed to makeEnum to disallow inter-type comparisons.
Wouldn't be that hard. I guess I'll do it, but I don't want to re-do this post ;-)

Regards,
Bengt Richter
Nov 22 '05 #10
On Sat, 19 Nov 2005 11:10:42 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@oz.net> wrote:
Ben Finney <bi****************@benfinney.id.au> wrote:
>Getting a numeric index might be useful in a language such as
>Pascal, with no built-in dict or sequence types. In Python, where
>any immutable object can be a dict key, and any sequence can be
>iterated, it seems of no use.
Does your concept of enumeration not have a fixed order of a set of
names?


It does. The values are iterable in the same order they were specified
when creating the Enum.
If it does, 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.
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? Then try to sort the keys(or items is the values are
misc different enums). I hit that, and changed __cmp__ to compare
(typename, <intvalue or other if not int subtype>) tuples. That sorts
items grouped by enum type if they're keys. I think you can always
pass a stricter cmp to sorted if you want to assert type equality.
To me the int correspondence is as expectable and natural as
a,b,c=range(3) (at least as a default) though I think different
enumerations should be different types.
That's a complete contradiction, then. If you want them to be
different types, you don't want them to be integers.

No, it's not a contradiction. Different int _sub_types are different
types ;-)
Note that the ordering of int values makes the instances nicely
sortable too, e.g.,
That only means the enum values need to compare in the same sequence;
it doesn't mean they need to correspond to integer values.

True.
But bottom line, I really thing the int base type is more than an
implementation detail. I think it's natural for an _ordered_ set of
names ;-)
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 ;-)

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. 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 ;-).

I haven't yet converted my generated enum classes to singletons,
but I did make it so you can define a named enumeration class and
iterate the class itself (its metaclass defines __getitem__). What
you get is the particular enum class instance, e.g. (... time passes,
never mind, I cached instanced for an effective singleton set of named numbers.

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.
This of course goes for the methods of the main class being constructed too,
so they are sneaked in via the metaclass before instantiating the class ;-/
(Anyone have an idea how to do this cleanly and have the same functionality
--being able to iterate the _class_ object etc.?

Anyway, this is my "feedback" ;-) What's missing in this?

----< makeenum.py >----------------------------------------------------------
def makeEnum(ename, names):
"""
Factory function to returns enumeration class with name ename,
and enumerating space-delimited names in names string.
The class is an int subtype, and instances have int values as
well as corresponding names. Different enum instances with the
same int value are distinct as dict keys, but equal used as ints,
though they are not directly comparable unless the same type.

The return class itself defines an iterator that will return
all possible instances in order. The class names property returns
a tuple of the names, and the len(TheClass) returns the number of
names or of the iteration sequence.
"""
global __global_for_mc_hack__
class __global_for_mc_hack__(type):
"""
metaclass to define methods on the enum class per se, and to pass
through closure-dependent methods (which see names & top) to the
returned class, as well as being target itself for closure-dependent
methods of the class (which can't be defined in the class body
since names in a class body are either local or module global).
"""
def __new__(mcls, cname, cbases, cdict):
cdict.update(mcls.edict)
return type.__new__(mcls, cname, cbases, cdict)
def __getattr__(cls, name):
"""make instances accessible by attribute name"""
if isinstance(name, basestring):
return cls(name) # make/retrieve-from-cache an instance
raise IndexError, '%r not a name in "%s"'%(i, cls.__name__)

# the closure cell variables
names = names.split()
top = len(names)
cache = {}

# define method functions outside class so they'll be closures accessing nested names
def __contains__(cls, other): return type(other)==cls and 0<=int(other)<top
def __getitem__(cls, i):
"""make class iterable and indexable, returning fresh instances with given values"""
if isinstance(i, basestring) and i in names or isinstance(i, (int, long)) and (0<=i<top):
return cls(i) # make an instance
raise IndexError, '%r out of range for "%s"'%(i, cls.__name__)
# stuff closure-type method functions into global metaclass to define methods
# of the enum class per se
__global_for_mc_hack__.__contains__ = __contains__
__global_for_mc_hack__.__len__ = lambda cls: len(names)
__global_for_mc_hack__.names = property(lambda cls: tuple(names))
__global_for_mc_hack__.__getitem__ = __getitem__

def __new__(cls, name=names[0]):
try: return cache[name]
except KeyError:
try:
i = names.index(name)
e = int.__new__(cls, i)
cache[name] = cache[i] = e
return e
except ValueError:
if isinstance(name, int) and 0<= name < top:
e = int.__new__(cls, name)
cache[name] = cache[names[name]] = e
return e
raise ValueError, 'illegal %s enum value %r'%(cls.__name__, name)
def __repr__(self): return '%s(%r)' %(self.__class__.__name__, names[self])

# pass closure-type method functions to global metaclass. Ick ;-/
__global_for_mc_hack__.edict = dict(
__new__ = __new__, __repr__=__repr__, __str__=__repr__)

class enum(int):
__metaclass__ = __global_for_mc_hack__
def __cmp__(self, other):
if isinstance(other, int): oval = int(other)
else: oval = other
# allow sort by type names on incompatible types XXX make optional??
return cmp( (type(self).__name__, int(self)),
(type(other).__name__, oval))

#assert type(self)==type(other), (
# 'incompatible cmp types: %s vs %s'%(type(self).__name__, type(other).__name__))
#return cmp(int(self), int(other))
def __hash__(self): return hash((int(self), type(self).__name__))
enum.__name__ = ename
del __global_for_mc_hack__
return enum
-----------------------------------------------------------------------------
I'll go look at PyPI now ;-)


Feedback appreciated :-)

The above in use looks like:
from makeenum import makeEnum
Count = makeEnum('Count', 'eeny meeny miny moe')
Count.names ('eeny', 'meeny', 'miny', 'moe') Count[1] Count('meeny') d = dict((c, int(c)) for c in Count)
d {Count('moe'): 3, Count('miny'): 2, Count('meeny'): 1, Count('eeny'): 0} Fruit = makeEnum('Fruit', 'apple banana peach pear')
d.update((c, int(c)) for c in Fruit)
d {Count('eeny'): 0, Fruit('peach'): 2, Count('moe'): 3, Fruit('apple'): 0, Count('miny'): 2, Fru
t('banana'): 1, Fruit('pear'): 3, Count('meeny'): 1} for it in sorted(d.items()): print '%20s: %r'%it ...
Count('eeny'): 0
Count('meeny'): 1
Count('miny'): 2
Count('moe'): 3
Fruit('apple'): 0
Fruit('banana'): 1
Fruit('peach'): 2
Fruit('pear'): 3
Fruit('pear') in Count False Fruit('pear') in Fruit True Fruit('plum') in Fruit Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "makeenum.py", line 65, in __new__
raise ValueError, 'illegal %s enum value %r'%(cls.__name__, name)
ValueError: illegal Fruit enum value 'plum' d[Fruit('pear')] 3 d[3] Traceback (most recent call last):
File "<stdin>", line 1, in ?
KeyError: 3 d[Fruit(3)] 3 d[Fruit('pear')] = 'juicy'
d[Fruit['pear']] 'juicy' lf = list(Fruit)
lf [Fruit('apple'), Fruit('banana'), Fruit('peach'), Fruit('pear')] lf2 = list(Fruit)
map(id, lf) [49320972, 49321068, 49321132, 49321164] map(id, lf2) [49320972, 49321068, 49321132, 49321164] len(Fruit) 4 Fruit <class 'makeenum.Fruit'> Fruit() Fruit('apple') Fruit(0) Fruit('apple') Fruit('apple') Fruit('apple') type(Fruit('apple')) <class 'makeenum.Fruit'> id(Fruit('apple')), id(lf[0]) (49320972, 49320972)

Almost forgot, I added attribute style access:
Fruit.pear Fruit('pear') d[Fruit.pear] 'juicy' d[Count(3)] 3 Count[3] Count('moe') d[Count.moe] 3 d[Fruit.pear] += ', very'
d[Fruit.pear] 'juicy, very'

Note, int(Fruit.pear) 3 d[3] Traceback (most recent call last):
File "<stdin>", line 1, in ?
KeyError: 3

isinstance(Fruit.pear, int) True type(Fruit.pear) <class 'makeenum.Fruit'> type(Fruit.pear).mro() [<class 'makeenum.Fruit'>, <type 'int'>, <type 'object'>]

But Fruit itself is derived, which is how the tricky methods work type(Fruit) <class 'makeenum.__global_for_mc_hack__'> type(Fruit).mro(type(Fruit))

[<class 'makeenum.__global_for_mc_hack__'>, <type 'type'>, <type 'object'>]

I guess an option could be passed to makeEnum to disallow inter-type comparisons.
Wouldn't be that hard. I guess I'll do it, but I don't want to re-do this post ;-)

Regards,
Bengt Richter
Nov 22 '05 #11
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.
--
Steven.

Nov 22 '05 #12
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.
--
Steven.

Nov 22 '05 #13
On Sat, 19 Nov 2005 21:45:40 +1100, Steven D'Aprano <st***@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
Nov 22 '05 #14
On Sat, 19 Nov 2005 21:45:40 +1100, Steven D'Aprano <st***@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
Nov 22 '05 #15
Bengt Richter <bo**@oz.net> wrote:
On Sat, 19 Nov 2005 11:10:42 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@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.
from enum import Enum
colours = Enum('red', 'blue', 'green')
for val in colours: ... print str(val)
...
red
blue
green

Why would the Enum *class* be iterable?
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".
colour_german = { colours.red: "rot", colours.green: "grün",
colours.blue: "blau" }
for val in colours:
... print colour_german[val]
...
rot
blau
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,
those won't compare against each other; there's no meaningful
relationship, since different enum instances are conceptually
different types.
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.
That sorts items grouped by enum type if they're keys.
Why should colours.blue compare before fruits.orange? How is that
meaningful?
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.
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.


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 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?

--
\ "The illiterate of the future will not be the person who cannot |
`\ read. It will be the person who does not know how to learn." |
_o__) -- Alvin Toffler |
Ben Finney
Nov 22 '05 #16
Bengt Richter <bo**@oz.net> wrote:
On Sat, 19 Nov 2005 11:10:42 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@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.
from enum import Enum
colours = Enum('red', 'blue', 'green')
for val in colours: ... print str(val)
...
red
blue
green

Why would the Enum *class* be iterable?
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".
colour_german = { colours.red: "rot", colours.green: "grün",
colours.blue: "blau" }
for val in colours:
... print colour_german[val]
...
rot
blau
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,
those won't compare against each other; there's no meaningful
relationship, since different enum instances are conceptually
different types.
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.
That sorts items grouped by enum type if they're keys.
Why should colours.blue compare before fruits.orange? How is that
meaningful?
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.
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.


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 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?

--
\ "The illiterate of the future will not be the person who cannot |
`\ read. It will be the person who does not know how to learn." |
_o__) -- Alvin Toffler |
Ben Finney
Nov 22 '05 #17
Steven D'Aprano <st***@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.


The Python docs don't really talk about such a thing. However, the
None name cannot be re-bound. Is that all that's required to have a
"constant" in Python?

If so, it's part of the assignment statement, and not something the
object itself can decide. True?
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?
Really hoping someone can come up with an answer for this.
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.


"from foo import *" is supported in the language, but is still bad
style. FrozenSet was *added* to the language (2.2), so that's evidence
that Guido thinks it's a good idea. But I'd still like direct
discussion: is making one's class instances immutable bad style?

--
\ "Say what you will about the Ten Commandments, you must always |
`\ come back to the pleasant fact that there are only ten of |
_o__) them." -- Henry L. Mencken |
Ben Finney
Nov 22 '05 #18
Steven D'Aprano <st***@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.


The Python docs don't really talk about such a thing. However, the
None name cannot be re-bound. Is that all that's required to have a
"constant" in Python?

If so, it's part of the assignment statement, and not something the
object itself can decide. True?
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?
Really hoping someone can come up with an answer for this.
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.


"from foo import *" is supported in the language, but is still bad
style. FrozenSet was *added* to the language (2.2), so that's evidence
that Guido thinks it's a good idea. But I'd still like direct
discussion: is making one's class instances immutable bad style?

--
\ "Say what you will about the Ten Commandments, you must always |
`\ come back to the pleasant fact that there are only ten of |
_o__) them." -- Henry L. Mencken |
Ben Finney
Nov 22 '05 #19
On Sun, 20 Nov 2005 08:56:33 +1100, Ben Finney wrote:
Steven D'Aprano <st***@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.


The Python docs don't really talk about such a thing. However, the
None name cannot be re-bound. Is that all that's required to have a
"constant" in Python?


I think None is a special case. The docs have said that None will become
a keyword in the future.
If so, it's part of the assignment statement, and not something the
object itself can decide. True?


Yes, that would be how I interpret constants: You want a name which can't
be re-bound to something else.

One work-around is to use the convention of writing the name in all caps:

CONSTANT = some_value

and then trust that your module user doesn't rebind CONSTANT. I'd like to
see support for something a little stronger than just "cross your fingers
and hope", without necessarily going all the way to Pascal's full
enforcement of constants. "We're all adults here" -- if somebody *really*
wants to rebind CONSTANT, I'm not going to stop them, but I want them to
jump through a hoop to do it, as a reminder that the module creator thinks
they shouldn't be doing it.

The obvious analogy is with name mangling of __private attributes in
classes.

> 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?


Really hoping someone can come up with an answer for this.


So am I :-)

Have you looked at the source for Frozenset? It was a pure Python module
in Python 2.3, before becoming a built-in in 2.4.

--
Steven.

Nov 22 '05 #20
On Sun, 20 Nov 2005 08:56:33 +1100, Ben Finney wrote:
Steven D'Aprano <st***@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.


The Python docs don't really talk about such a thing. However, the
None name cannot be re-bound. Is that all that's required to have a
"constant" in Python?


I think None is a special case. The docs have said that None will become
a keyword in the future.
If so, it's part of the assignment statement, and not something the
object itself can decide. True?


Yes, that would be how I interpret constants: You want a name which can't
be re-bound to something else.

One work-around is to use the convention of writing the name in all caps:

CONSTANT = some_value

and then trust that your module user doesn't rebind CONSTANT. I'd like to
see support for something a little stronger than just "cross your fingers
and hope", without necessarily going all the way to Pascal's full
enforcement of constants. "We're all adults here" -- if somebody *really*
wants to rebind CONSTANT, I'm not going to stop them, but I want them to
jump through a hoop to do it, as a reminder that the module creator thinks
they shouldn't be doing it.

The obvious analogy is with name mangling of __private attributes in
classes.

> 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?


Really hoping someone can come up with an answer for this.


So am I :-)

Have you looked at the source for Frozenset? It was a pure Python module
in Python 2.3, before becoming a built-in in 2.4.

--
Steven.

Nov 22 '05 #21
On Sun, 20 Nov 2005 08:42:48 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@oz.net> wrote:
On Sat, 19 Nov 2005 11:10:42 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
>Bengt Richter <bo**@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
Nov 22 '05 #22
On Sun, 20 Nov 2005 08:42:48 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
Bengt Richter <bo**@oz.net> wrote:
On Sat, 19 Nov 2005 11:10:42 +1100 (EST), Ben Finney <bi****************@benfinney.id.au> wrote:
>Bengt Richter <bo**@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
Nov 22 '05 #23

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

48
by: Andrew Quine | last post by:
Hi Just read this article http://www.artima.com/intv/choices.html. Towards the end of the dicussions, when asked "Did you consider including support for the concept of immutable directly in C#...
0
by: Ben Finney | last post by:
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. ...
90
by: Ben Finney | last post by:
Howdy all, How can a (user-defined) class ensure that its instances are immutable, like an int or a tuple, without inheriting from those types? What caveats should be observed in making...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing,...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.