Home > Back-end >  Python Enum with exception: TypeError: Attempted to reuse key:
Python Enum with exception: TypeError: Attempted to reuse key:

Time:10-18

I'm trying to redefine an enum when it fails, but then an error is raised.

My code looks like the following:

from enum import Enum

class FooEnum(Enum):
    try:
        foo = 3/0
    except Exception as my_exception_instance:
        print('An error occurred:', my_exception_instance)
        foo=0

The goal is that 3/0 will raise an exception and then re-define foo. However, when I run it as is, the print message is shown but another error is thrown, which does not make sense for me. Here goes the output and stack trace:

An error occurred: division by zero
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-489d2391f28b> in <module>
      1 from enum import Enum
      2 
----> 3 class FooEnum(Enum):
      4     try:
      5         foo = 3/0

<ipython-input-10-489d2391f28b> in FooEnum()
      6     except Exception as my_exception_instance:
      7         print('An error occurred:', my_exception_instance)
----> 8         foo=0

/usr/lib/python3.6/enum.py in __setitem__(self, key, value)
     90         elif key in self._member_names:
     91             # descriptor overwriting an enum?
---> 92             raise TypeError('Attempted to reuse key: %r' % key)
     93         elif not _is_descriptor(value):
     94             if key in self:

TypeError: Attempted to reuse key: 'my_exception_instance'

The only way to get rid of this error, is by removing the usage of the exception when catching it:

from enum import Enum

class FooEnum(Enum):
    try:
        foo = 3/0
    except:
        print('An error occurred')
        foo=0

Then it just outputs: An error occurred

I'm using python 3.6.9


EDIT The following code is closer to my use case:

import tensorflow as tf

from enum import Enum

class VisualModels(Enum):
    

    try:
        MobileNet = tf.keras.applications.MobileNetV2
    except Exception as e:
        print(f'MobileNetV2 Not found, using MobileNet instead. Error: {e}.')
        MobileNet = tf.keras.applications.MobileNet

    # more models are defined similarly

CodePudding user response:

The reason that is happening is:

  • _EnumDict tracks all used names
  • _EnumDict thinks my_exception_instance should be a member
  • Python clears the as variable when leaving the except clause
    • by assigning None to my_exception_instance (and then deleting the variable)
    • causing _EnumDict to think a key is being reused

One workaround (as of Python 3.7) is to add my_exception_instance to an _ignore_1 attribute:

class FooEnum(Enum):
    _ignore_ = 'my_exception_instance'
    try:
        foo = 3/0
    except Exception as my_exception_instance:
        print('An error occurred:', my_exception_instance)
        foo=0

The other workaround is make my_exception_instance be a global:

class FooEnum(Enum):
    global my_exception_instance
    try:
        foo = 3/0
    except Exception as my_exception_instance:
        print('An error occurred:', my_exception_instance)
        foo=0

Finally, if you don't want the try/except in the body of your enum:

class FallbackEnum(Enum):
    def __new__(cls, *values):
        # first actual value wins
        member = object.__new__(cls)
        fallback = False
        for v in values:
            try:
                member._value_ = eval(v)
                break
            except Exception as e:
                print('%s error: %s' % (v, e))
                fallback = True
                continue
        else:
            # never found a value
            raise ValueError('no valid value found')
        # if we get here, we found a value
        if fallback:
            # if the first value didn't work, print the one we did use
            print('  using %s' % v)
        return member

and in use:

>>> class FooEnum(FallbackEnum):
...     foo = '3/0', '0'
... 
3/0 error: division by zero
  using 0

>>> list(FooEnum)
[<FooEnum.foo: 0>]

1 You can use aenum if stuck at Python 3.6.

Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

  • Related