473,739 Members | 6,655 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

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 2133
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(nam e)
... 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__':__re pr__, '__str__':__rep r__,
... 'names':getname s()})
...
... 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(nam e)
... 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__':__re pr__, '__str__':__rep r__,
... 'names':getname s()})
...
... 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).__na me__)
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(rang e(3)) '[0, 1, 2] has 3 elements' verboselen(rang e(0)) '[] has 0 elements' verboselen(rang e(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).__na me__)
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(rang e(3)) '[0, 1, 2] has 3 elements' verboselen(rang e(0)) '[] has 0 elements' verboselen(rang e(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(mc ls.edict)
return type.__new__(mc ls, 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__(cl s, other): return type(other)==cl s and 0<=int(other)<t op
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__.__conta ins__ = __contains__
__global_for_mc _hack__.__len__ = lambda cls: len(names)
__global_for_mc _hack__.names = property(lambda cls: tuple(names))
__global_for_mc _hack__.__getit em__ = __getitem__

def __new__(cls, name=names[0]):
try: return cache[name]
except KeyError:
try:
i = names.index(nam e)
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(othe r, int): oval = int(other)
else: oval = other
# allow sort by type names on incompatible types XXX make optional??
return cmp( (type(self).__n ame__, int(self)),
(type(other).__ name__, oval))

#assert type(self)==typ e(other), (
# 'incompatible cmp types: %s vs %s'%(type(self) .__name__, type(other).__n ame__))
#return cmp(int(self), int(other))
def __hash__(self): return hash((int(self) , type(self).__na me__))
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.p y", 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('app le')) <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(Frui t.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.__glo bal_for_mc_hack __'> type(Fruit).mro (type(Fruit))

[<class 'makeenum.__glo bal_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

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

Similar topics

48
3506
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# and the CLR?" Anders' reply included this comment: "The concept of an immutable object is very useful, but it's just up to the author to say that it's immutable."
0
1029
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. This raises questions. Is there any difference between a Python immutable value, and a constant? I suppose "constant" also implies that the *name* binds
90
4402
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 immutable instances? -- \ "Love is the triumph of imagination over intelligence." -- |
0
8792
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 effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
9337
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 tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
1
9266
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
8215
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, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
6754
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 presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
4570
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
1
3280
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
2
2748
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
3
2193
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

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.