# Built-in StrEnum

**URL:** <https://discuss.python.org/t/built-in-strenum/4192>\
**Category:** Ideas\
**Created:** [May 14, 2020, 3:15pm UTC](https://discuss.python.org/t/built-in-strenum/4192 "2020-05-14T15:15:28Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [May 14, 2020, 3:15pm UTC](https://discuss.python.org/t/built-in-strenum/4192/1 "2020-05-14T15:15:28Z")

</div>

Many python libraries use magic string constants to represent enums. The fact that enums exist at all, in any language, is evidence of their superiority in these use cases: it’s more discoverable, better for type checking, easier to guarantee the interface, and easier to test. Python considers enums worthwhile, which is why they’re in the standard library, but in downstream libraries it’s much more common to see magic string constants. In the standard library itself, this pattern is also common, but in much of the core there is good reason for that (things which happen before the `import` machinery has kicked in, or where you want to avoid importing `enum` and its dependencies), so I’m happy to let that slide.

Downstream libraries, however, do not have a consistent upgrade path to go from magic strings to enums. Having to support both the new enum and the old string all the way through their library is a pain, and involves double the equality checks. For some libraries, there are so many code snippets available in so many places that you could never get rid of the magic string interface.

For wrapping underlying libraries which use integers for enums, there is the `IntEnum` and `IntFlag`: these come with a warning against their use because the integers don’t semantically mean anything, they’re just a convenient crutch for languages without good enum support. But they prove that the python standard library sees the value in wrapping unergonomic, undiscoverable constants in enums, where they’re being used to semantically represent enums anyway.

An `Enum` subclass which also subclasses `str` would provide one (“and preferably only one”) way forward, where new code and documentation could refer to the enum and old code/examples would still work. Compatibility would be maintained (even including where those magic strings are passed through to underlying libraries, based on my experience with rust/pyo3), and everyone benefits from the advantages of enums.

Such a strategy is already possible. Several downstream libraries include their own implementation of StrEnum. The code required is very small, which means many would prefer not to include another dependency for it: this means dozens of different implementations with slightly different features and inconsistencies. It also means many people wasting a lot of time and space. The code being so small means that very little API surface/ maintenance overhead would be added to the standard library.

Here is the example I tend to use (originally a fork of the unmaintained [StrEnum](https://pypi.org/project/StrEnum/)):

```python
import enum

class StrEnum(str, enum.Enum):
    def __new__ (cls, *args):
        for arg in args:
            if not isinstance(arg, (str, enum.auto)):
                raise TypeError(
                    "Values of StrEnums must be strings: {} is a {}".format(
                        repr(arg), type(arg)
                    )
                )
        return super(). __new__ (cls, *args)

    def __str__ (self):
        return self.value

    # The first argument to this function is documented to be the name of the
    # enum member, not `self`:
    # https://docs.python.org/3.6/library/enum.html#using-automatic-values
    def _generate_next_value_(name, *_):
        return name

```

I believe it to be more complete than StrEnum and fastapi\_utils.enums.StrEnum, and more standard/ lower maintenance than AnyStrEnum.

tl;dr Python already accepts that enums are worthwhile, lots of people use magic string constants where they should use enums, a built-in StrEnum would provide a consistent and easy upgrade path where everyone benefits.

---

<div class="post-metadata">

**Author:** ![stoneleaf](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stoneleaf/32/88_2.png) [@stoneleaf](https://discuss.python.org/u/stoneleaf)\
**Post date:** [May 14, 2020, 3:35pm UTC](https://discuss.python.org/t/built-in-strenum/4192/2 "2020-05-14T15:35:43Z")

</div>

This is an interesting idea. Can you give an example where `*args` is more than a single `str`?

---

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [May 14, 2020, 6:30pm UTC](https://discuss.python.org/t/built-in-strenum/4192/3 "2020-05-14T18:30:08Z")

</div>

I inherited that from the StrEnum package - I think you’re right, it doesn’t need that. Strictly speaking, the whole ` __new__ ` override isn’t needed.

`IntEnum` silently casts the value to an integer, only throwing an error for a value which can’t be cast. This isn’t _too_ surprising for the case of an integer, where people have e.g. numpy number types, float/int confusion, etc.: not to mention, if you create an `IntEnum` using a string, you probably meant that string as a number - basically, there’s a limited number of things which can be turned into integers, and most of them are pretty integer-like. On the other hand, everything can be turned into a string, even things which aren’t very string-y, so we might prefer to explicitly check that the passed arguments are strings (and throw the associated error) rather than doing any implicit casting.

---

<div class="post-metadata">

**Author:** ![stoneleaf](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stoneleaf/32/88_2.png) [@stoneleaf](https://discuss.python.org/u/stoneleaf)\
**Post date:** [May 14, 2020, 6:34pm UTC](https://discuss.python.org/t/built-in-strenum/4192/4 "2020-05-14T18:34:09Z")

</div>

Very good points.

---

<div class="post-metadata">

**Author:** ![nicoddemus](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nicoddemus/32/907_2.png) [@nicoddemus](https://discuss.python.org/u/nicoddemus)\
**Post date:** [May 18, 2020, 2:01pm UTC](https://discuss.python.org/t/built-in-strenum/4192/5 "2020-05-18T14:01:22Z")

</div>

FWIW, when I upgrade from magic constants to `Enum`, this is how I often do it to maintain backward compatibility:

```python
# old code
FILE = "file"
FOLDER = "folder"

def foo(ft):
    if ft == FILE:
        print("it is a file")
    elif ft == FOLDER:
        print("it is a folder")
    else:
        raise TypeError(f"Invalid file type: {ft}")

# usage
foo(FILE)
foo(FOLDER)
foo("invalid")

```

Here’s the output:

```auto
it is a file
it is a folder
Traceback (most recent call last):
  File "foo.py", line 22, in <module>
    foo("invalid")
  File "foo.py", line 17, in foo
    raise TypeError(f"Invalid file type: {ft}")
TypeError: Invalid file type: invalid

```

```python
# new code
class FileType(Enum):
    file = "file"
    folder = "folder"

FILE = FileType.file
FOLDER = FileType.folder

def foo(ft):
    ft = FileType(ft)
    if ft == FILE:
        print("it is a file")
    elif ft == FOLDER:
        print("it is a folder")
    else:
        raise TypeError(f"Invalid file type: {ft}") # never reached anymore

# usage
foo(FILE)
foo(FOLDER)
foo("invalid")

```

Here’s the output:

```auto
it is a file
it is a folder
ValueError: 'invalid' is not a valid FileType

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "foo.py", line 45, in <module>
    foo("invalid")
  File "foo.py", line 34, in foo
    ft = FileType(ft)
  File "C:\Users\Bruno\AppData\Local\Programs\Python\Python37\lib\enum.py", line 310, in __call__
    return cls. __new__ (cls, value)
  File "C:\Users\Bruno\AppData\Local\Programs\Python\Python37\lib\enum.py", line 564, in __new__
    raise exc
  File "C:\Users\Bruno\AppData\Local\Programs\Python\Python37\lib\enum.py", line 548, in __new__
    result = cls._missing_(value)
  File "C:\Users\Bruno\AppData\Local\Programs\Python\Python37\lib\enum.py", line 577, in _missing_
    raise ValueError("%r is not a valid %s" % (value, cls. __name__ ))
ValueError: 'invalid' is not a valid FileType

```

Not sure if it covers all your use case though.

Cheers,

---

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [May 18, 2020, 3:18pm UTC](https://discuss.python.org/t/built-in-strenum/4192/6 "2020-05-18T15:18:33Z")

</div>

Yes, that’s one way of doing it. IMO it’s not ideal because it requires the cast to `FileType` everywhere that the enum is passed into a function, potentially adds to or changes the error types thrown by the function, and changes the types of possibly public constants.

The upgrade path isn’t impossible today, it would just be smoother and more consistent with a built-in StrEnum (in my opinion).

---

<div class="post-metadata">

**Author:** ![nicoddemus](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nicoddemus/32/907_2.png) [@nicoddemus](https://discuss.python.org/u/nicoddemus)\
**Post date:** [May 18, 2020, 4:22pm UTC](https://discuss.python.org/t/built-in-strenum/4192/7 "2020-05-18T16:22:02Z")

</div>

Definitely, just wanted to leave that example because I think it is the cleaner option from the POV of the user; for the developer not so much because as you say you need now to update every function that deals with the constants.

This of course does not invalidate your proposal, it is just another workaround. 👍

---

<div class="post-metadata">

**Author:** ![stoneleaf](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stoneleaf/32/88_2.png) [@stoneleaf](https://discuss.python.org/u/stoneleaf)\
**Post date:** [November 16, 2020, 9:12pm UTC](https://discuss.python.org/t/built-in-strenum/4192/8 "2020-11-16T21:12:41Z")

</div>

`StrEnum` was merged [on Sep 21](https://bugs.python.org/issue41816) so it’ll be available in 3.10.

---

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [November 16, 2020, 9:54pm UTC](https://discuss.python.org/t/built-in-strenum/4192/9 "2020-11-16T21:54:39Z")

</div>

Amazing! Thank you so much, really looking forward to seeing it in action.

---

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [November 16, 2020, 11:56pm UTC](https://discuss.python.org/t/built-in-strenum/4192/10 "2020-11-16T23:56:36Z")

</div>

Hacked some bits off your PRs to start on a `backports` package, just need to figure out what’s going on with the MRO in 3.6 to fix one of the mixin tests. [https://github.com/clbarnes/backports.strenum](https://github.com/clbarnes/backports.strenum)

---

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [November 19, 2020, 3:50pm UTC](https://discuss.python.org/t/built-in-strenum/4192/11 "2020-11-19T15:50:28Z")

</div>

`backports.strenum` now exists for python 3.8+ [https://pypi.org/project/backports.strenum/](https://pypi.org/project/backports.strenum/)

---

<div class="post-metadata">

**Author:** ![ericbn](https://avatars.discourse-cdn.com/v4/letter/e/a5b964/32.png) [@ericbn](https://discuss.python.org/u/ericbn)\
**Post date:** [October 4, 2021, 3:57pm UTC](https://discuss.python.org/t/built-in-strenum/4192/12 "2021-10-04T15:57:16Z")

</div>

Hi. Not overriding `_generate_next_value_` in `StrEnum` was a design decision?

With it, we could write

```auto
class Color(StrEnum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()

```

---

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [October 4, 2021, 4:59pm UTC](https://discuss.python.org/t/built-in-strenum/4192/13 "2021-10-04T16:59:47Z")

</div>

I’m not sure what you mean - `_generate_next_value_` is implemented in both CPython’s enum.StrEnum ( [cpython/enum.py at main · python/cpython · GitHub](https://github.com/python/cpython/blob/main/Lib/enum.py#L1093) ) and `backports.strenum.StrEnum` ([backports.strenum/strenum.py at main · clbarnes/backports.strenum · GitHub](https://github.com/clbarnes/backports.strenum/blob/main/backports/strenum/strenum.py#L35) ).

---

<div class="post-metadata">

**Author:** ![ericbn](https://avatars.discourse-cdn.com/v4/letter/e/a5b964/32.png) [@ericbn](https://discuss.python.org/u/ericbn)\
**Post date:** [October 4, 2021, 5:16pm UTC](https://discuss.python.org/t/built-in-strenum/4192/14 "2021-10-04T17:16:56Z")

</div>

Oh I totally missed that, sorry for the noise!

---

<div class="post-metadata">

**Author:** ![clbarnes](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/clbarnes/32/1052_2.png) [@clbarnes](https://discuss.python.org/u/clbarnes)\
**Post date:** [October 5, 2021, 2:02pm UTC](https://discuss.python.org/t/built-in-strenum/4192/15 "2021-10-05T14:02:44Z")

</div>

As this is one of the places it’s being discussed, worth noting that this has been bumped from 3.10 to 3.11 and may require a PEP to make it in at all: [Issue 44559: Enum: revert to 3.9 - Python tracker](https://bugs.python.org/issue44559) @stoneleaf is StrEnum caught up in this or was it more about the dunder methods of other Enum types? I don’t have any experience in PEP-writing but I’d be very happy to be involved in discussions around StrEnum if I can help at all!
