[Python-Dev] constant/enum type in stdlib

Nick Coghlan ncoghlan at gmail.com
Sat Nov 27 11:51:38 CET 2010


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.

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.

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

==========================================================================
# 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.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-Dev mailing list