[Python-Dev] constant/enum type in stdlib

Michael Foord fuzzyman at voidspace.org.uk
Sat Nov 27 15:01:22 CET 2010


On 27/11/2010 10:51, Nick Coghlan wrote:
> On Thu, Nov 25, 2010 at 3:41 AM, Michael Foord
> <fuzzyman at voidspace.org.uk>  wrote:
>> Can you explain what you see as the difference?
>>
>> I'm not particularly interested in type validation but I like the fact that
>> typical enum APIs allow you to group constants: the generated constant class
>> acts as a namespace for all the defined constants.
> The problem with blessing one particular "enum API" is that people
> have so many different ideas as to what an enum API should look like.
>

There actually seemed to be quite a bit of agreement around basic 
functionality though.

> However, the one thing they all have in common is the ability to take
> a value and give it a name, then present *both* of those in debugging
> information.

And this is the most important functionality. I would say that the 
grouping (namespacing) of constants is also useful, provided by *most* 
Python enum APIs and easy to implement without over complexifying the API.

(Note that there is no *particular* hurry to get this into 3.2 - the 
beta is due imminently. I wouldn't object to it )

>> Are you just suggesting something along the lines of:
>>
>> class NamedConstant(int):
>> def __new__(cls, name, val):
>> return int.__new__(cls, val)
>>
>> def __init__(self, name, val):
>> self._name = name
>>
>> def __repr__(self):
>> return '<NamedConstant %s>' % self._name
>>
>> FOO = NamedConstant('FOO', 3)
>>
>> In general the less features the better, but I'd like a few more features
>> than that. :-)
> Not quite. I'm suggesting a factory function that works for any value,
> and derives the parent class from the type of the supplied value.
> However, what you wrote is still the essence of the idea - we would be
> primarily providing a building block that makes it easier for people
> to *create* enum APIs if they want to, but for simple use cases (where
> all they really wanted was the enhanced debugging information) they
> wouldn't need to bother. In the standard library, wherever we do
> "enum-like things" we would switch to using named values where it
> makes sense to do so.
>
> Doing so may actually make sense for more than just constants - it may
> make sense for significant mutable globals as well.

Very interesting proposal (typed named values rather than just named 
constants). It doesn't handle flag values, which I would still like, but 
that only really makes sense for integers (sets can be OR'd but their 
representation is already understandable). Perhaps the integer named 
type could be special cased for that.

Without the grouping functionality (associating a bunch of names 
together) you lose the 'from_name' functionality. Guido was in favour of 
this, and it is an obvious feature where you have grouping: 
http://mail.python.org/pipermail/python-dev/2010-November/105912.html

"""I expect that the API to convert between enums and bare ints should be
i = int(e) and e = <enumclass>(i). It would be nice if s = str(e) and
e = <enumclass>(s) would work too."""

This wouldn't work with your suggested implementation (as it is). 
Grouping and mutable "named values" could be inefficient and have issues 
around identity / equality. Maybe restrict the API to the immutable 
primitives.

All the best,

Michael
> ==========================================================================
> # Implementation (more than just a sketch, since it handles some
> interesting corner cases)
> import functools
> @functools.lru_cache()
> def _make_named_value_type(base_type):
>      class _NamedValueType(base_type):
>          def __new__(cls, name, value):
>              return base_type.__new__(cls, value)
>          def __init__(self, name, value):
>              self.__name = name
>              super().__init__(value)
>          @property
>          def _name(self):
>              return self.__name
>          def _raw(self):
>              return base_type(self)
>          def __repr__(self):
>              return "{}={}".format(self._name, super().__repr__())
>          if base_type.__str__ is object.__str__:
>              __str__ = base_type.__repr__
>      _NamedValueType.__name__ = "Named<{}>".format(base_type.__name__)
>      return _NamedValueType
>
> def named_value(name, value):
>      return _make_named_value_type(type(value))(name, value)
>
> def set_named_values(namespace, **kwds):
>      for k, v in kwds.items():
>          namespace[k] = named_value(k, v)
>
> x = named_value("FOO", 1)
> y = named_value("BAR", "Hello World!")
> z = named_value("BAZ", dict(a=1, b=2, c=3))
>
> print(x, y, z, sep="\n")
> print("\n".join(map(repr, (x, y, z))))
> print("\n".join(map(str, map(type, (x, y, z)))))
>
> set_named_values(globals(), foo=x._raw(), bar=y._raw(), baz=z._raw())
> print("\n".join(map(repr, (foo, bar, baz))))
> print(type(x) is type(foo), type(y) is type(bar), type(z) is type(baz))
>
> ==========================================================================
>
> # Session output for the last 6 lines
>>>> print(x, y, z, sep="\n")
> 1
> Hello World!
> {'a': 1, 'c': 3, 'b': 2}
>
>>>> print("\n".join(map(repr, (x, y, z))))
> FOO=1
> BAR='Hello World!'
> BAZ={'a': 1, 'c': 3, 'b': 2}
>
>>>> print("\n".join(map(str, map(type, (x, y, z)))))
> <class '__main__.Named<int>'>
> <class '__main__.Named<str>'>
> <class '__main__.Named<dict>'>
>
>>>> set_named_values(globals(), foo=x._raw(), bar=y._raw(), baz=z._raw())
>>>> print("\n".join(map(repr, (foo, bar, baz))))
> foo=1
> bar='Hello World!'
> baz={'a': 1, 'c': 3, 'b': 2}
>
>>>> print(type(x) is type(foo), type(y) is type(bar), type(z) is type(baz))
> True True True
>
> For "normal" use, such objects would look like ordinary instances of
> their class. They would only behave differently when their
> representation is printed (prepending their name), or when their type
> is interrogated (being an instance of the named subclass rather than
> the ordinary type).
>
> Cheers,
> Nick.
>


-- 

http://www.voidspace.org.uk/

READ CAREFULLY. By accepting and reading this email you agree,
on behalf of your employer, to release me from all obligations
and waivers arising from any and all NON-NEGOTIATED agreements,
licenses, terms-of-service, shrinkwrap, clickwrap, browsewrap,
confidentiality, non-disclosure, non-compete and acceptable use
policies (”BOGUS AGREEMENTS”) that I have entered into with your
employer, its partners, licensors, agents and assigns, in
perpetuity, without prejudice to my ongoing rights and privileges.
You further represent that you have the authority to release me
from any BOGUS AGREEMENTS on behalf of your employer.



More information about the Python-Dev mailing list