Skip to content

StrEnum.__getitem__ raises TypeError #128659

Open
@injust

Description

@injust

Bug report

Bug description:

from enum import Enum, StrEnum, auto

class Foo(Enum):
    one = auto()
    two = auto()
    
class Bar(StrEnum):
    one = auto()
    two = auto()
    
names = ["one", "two"]

print([Foo[name] for name in names])
print(list(map(Foo.__getitem__, names)))

print([Bar[name] for name in names])
print(list(map(Bar.__getitem__, names)))  # TypeError: expected 1 argument, got 0

CPython versions tested on:

3.12, 3.13

Operating systems tested on:

macOS

Activity

added
type-bugAn unexpected behavior, bug, or error
on Jan 9, 2025
self-assigned this
on Jan 9, 2025
tom-pytel

tom-pytel commented on Jan 9, 2025

@tom-pytel
Contributor

str class slots are overriding EnumType mapping for __getitem__ and others in StrEnum for certain uses.

>>> Foo.__getitem__
<bound method EnumType.__getitem__ of <enum 'Foo'>>

>>> Bar.__getitem__
<slot wrapper '__getitem__' of 'str' objects>

They work implicitly but not explicitly:

>>> Bar['one']
<Bar.one: 'one'>

>>> Bar.__getitem__('one')
Traceback (most recent call last):
  File "<python-input-19>", line 1, in <module>
    Bar.__getitem__('one')
    ~~~~~~~~~~~~~~~^^^^^^^
TypeError: expected 1 argument, got 0

>>> Bar.__getitem__(Bar, 'one')
Traceback (most recent call last):
  File "<python-input-20>", line 1, in <module>
    Bar.__getitem__(Bar, 'one')
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^
TypeError: descriptor '__getitem__' requires a 'str' object but received a 'EnumType'

Because Bar['one'] calls EnumType.__getitem__() and Bar.__getitem__('one') calls str.__getitem__(). This also applies to other magics like __contains__ or __len__:

>>> Bar.__contains__
<slot wrapper '__contains__' of 'str' objects>

>>> Bar.__len__
<slot wrapper '__len__' of 'str' objects>

>>> len(Bar)
2

>>> Bar.__len__()
Traceback (most recent call last):
  File "<python-input-22>", line 1, in <module>
    Bar.__len__()
    ~~~~~~~~~~~^^
TypeError: descriptor '__len__' of 'str' object needs an argument

>>> Bar.__len__(Bar)
Traceback (most recent call last):
  File "<python-input-23>", line 1, in <module>
    Bar.__len__(Bar)
    ~~~~~~~~~~~^^^^^
TypeError: descriptor '__len__' requires a 'str' object but received a 'EnumType'

>>> Bar.__len__('abc')
3

Quick hack fix, add the following to StrEnum to re-override methods overridden by str (if this is the desired behavior):

    # re-override methods overridden by str, see gh-128659
    __contains__ = classmethod(EnumType.__contains__)
    __getitem__ = classmethod(EnumType.__getitem__)
    __iter__ = classmethod(EnumType.__iter__)
    __len__ = classmethod(EnumType.__len__)

Before:

>>> Foo.__getitem__
<bound method EnumType.__getitem__ of <enum 'Foo'>>
>>> Bar.__getitem__
<slot wrapper '__getitem__' of 'str' objects>

After:

>>> Foo.__getitem__
<bound method EnumType.__getitem__ of <enum 'Foo'>>
>>> Bar.__getitem__
<bound method EnumType.__getitem__ of <enum 'Bar'>>
>>> Bar.__getitem__('one')
<Bar.one: 'one'>
serhiy-storchaka

serhiy-storchaka commented on Jan 13, 2025

@serhiy-storchaka
Member

Then what Bar.one[0] will return?

tom-pytel

tom-pytel commented on Jan 13, 2025

@tom-pytel
Contributor

Then what Bar.one[0] will return?

It will not act as a str with respect to those magics as you point out but rather as an EnumType (see qualifier above "if this is the desired behavior"). So maybe not the desired behavior and the original code should just be invalid?

ethanfurman

ethanfurman commented on Jan 13, 2025

@ethanfurman
Member

I'll need to get some tests written for the expected behavior of those dunders.

serhiy-storchaka

serhiy-storchaka commented on Jan 13, 2025

@serhiy-storchaka
Member

We need different, well defined behavior for Bar['one'] and Bar.one[0], len(Bar) and len(Bar.one), etc. An enum class should behave as other enum classes, and an enum instance should behave as other instance of a parent class.

As for the dunder methods, we have precedents. For example, int.__or__ is a method that corresponds to an instance operation, while str.__or__ is a method that corresponds to a type operation.

>>> int.__or__
<slot wrapper '__or__' of 'int' objects>
>>> int.__or__(1, 2)
3
>>> str.__or__
<method-wrapper '__or__' of type object at 0x563a4b54f960>
>>> str.__or__(int)
str | int

So I think we should not change anything in StrEnum, at least not before we change the larger image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibPython modules in the Lib dirtype-bugAn unexpected behavior, bug, or error

Projects

No projects

Milestone

No milestone

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @injust@serhiy-storchaka@ethanfurman@ZeroIntensity@tom-pytel

      Issue actions

        `StrEnum.__getitem__` raises `TypeError` · Issue #128659 · python/cpython