Skip to main content

A fast pure-python implementation of Enum

Project description

fast_enum

A fast Enum implementation for Python

The purpose is to replace Python3.4+ standard library Enum.

The main objective to using standard library's Enum is that it's super slow.

Features implemented:

  • as in stdlib's enums all Enum members are instances of the Enum itself
type(LightEnum.ONE)
# <class 'LightEnum'>
  • all enum members have at least name and value properties; the name property is a string containing the class member's name itself; the value property contains the value assigned
class ValuesGivenEnum(metaclass=FastEnum):
   ONE: 'ValuesGivenEnum' = 1
   FOUR: 'ValuesGivenEnum' = 4
   ELEVEN: 'ValuesGivenEnum' = 11

ValuesGivenEnum.FOUR
# <ValuesGivenEnum.FOUR: 4>
ValuesGivenEnum.FOUR.value
# 4
ValuesGivenEnum.FOUR.name
# 'FOUR'
  • a lightweight form of enum declaration is possible
class LightEnum(metaclass=FastEnum):
    ONE: 'LightEnum'
    TWO: 'LightEnum'
    THREE: 'LightEnum'

When this form is used it is strictly required that members are "type-hinted" as instances of the enum class. Otherwise they will remain just as property/attributes annotations deep inside the cls.__annotations__

  • an enum could be accessed by value
LightEnum(1)
# <LightEnum.ONE: 1>
  • or by name
LightEnum['ONE']
# <LightEnum.ONE: 1>
  • it is possible to mix lightweight declaration and a value-provided one in the same class:
class MixedEnum(metaclass=FastEnum):
    _ZERO_VALUED = 1
    AUTO_ZERO: 'MixedEnum'
    ONE: 'MixedEnum' = 1
    AUTO_ONE: 'MixedEnum'
    TWO: 'MixedEnum' = 2

MixedEnum(1)
# <MixedEnum.ONE: 1>
MixedEnum.AUTO_ZERO
# <MixedEnum.AUTO_ZERO: 0>
MixedEnum.AUTO_ONE
# <MixedEnum.ONE: 1>

When this form is used, if there are more than one Enum with the same value as a result (MixedEnum.AUTO_ONE.value and MixedEnum.ONE.value in this example) all subsequent enums are rendered as just aliases to the first declared (the order of declaration is: first value-provided enums then lightweight forms so auto-valued will always become aliases, not vice versa). The auto-valued enums value provider is independent from value-provided ones.

  • as shown in the previous example, a special attribute _ZERO_VALUED could be provided in class declaration; given it's value renders to True in boolean context auto-valued enums will start from zero instead of integer 1; The _ZERO_VALUED attribute is erased from the resulting enum type

  • an enum creation can be hooked with 'late-init'. If a special method def __init_late(self): ... is provided within enum class' declaration, it's run for every enum instance created after all of them are created successfully

class HookedEnum(metaclass=FastEnum):
    halved_value: 'HookedEnum'

    __slots__ = ('halved_value',)

    def __init_late(self):
        # noinspection PyTypeChecker
        self.halved_value: 'HookedEnum' = self.__class__(self.value // 2)

    ZERO: 'HookedEnum' = 0
    ONE: 'HookedEnum' = 1
    TWO: 'HookedEnum' = 2
    THREE: 'HookedEnum' = 3

HookedEnum.ZERO.halved_value
#<HookedEnum.ZERO: 0>
HookedEnum.ONE.halved_value
#<HookedEnum.ZERO: 0>
HookedEnum.TWO.halved_value
#<HookedEnum.ONE: 1>
HookedEnum.THREE.halved_value
#<HookedEnum.ONE: 1>
  • enums are singletons
from pickle import dumps, loads
o = LightEnum.ONE
r = loads(dumps(o))
id(o)
# 139649196736456
id(r)
# 139649196736456
o is r
# True
  • enums are hashable
list(LightEnum)
# [<LightEnum.ONE: 1>, <LightEnum.TWO: 2>, <LightEnum.THREE: 3>]
set(LightEnum)
# {<LightEnum.ONE: 1>, <LightEnum.TWO: 2>, <LightEnum.THREE: 3>}
  • enums are easily extended if one needs
class ExtendedEnum(metaclass=FastEnum):
    description: Text
    __slots__ = ('description',)

    def __init__(self, value, description, name):
        self.value = value
        self.name = name
        self.description = description

    def describe(self):
        return self.description

    RED = 'red', 'a color of blood'
    GREEN = 'green', 'a color of grass in the spring'

ExtendedEnum.GREEN
# <ExtendedEnum.GREEN: green>
str(ExtendedEnum.GREEN)
# 'ExtendedEnum.GREEN'
ExtendedEnum.GREEN.name
# 'GREEN'
ExtendedEnum.GREEN.value
# 'green'
ExtendedEnum.GREEN.description
# 'a color of grass in the spring'
ExtendedEnum.GREEN.describe()
# 'a color of grass in the spring'

In case an enum has extended set of fields it it must be guaranteed that the __init__ method has the name argument in the last position. It's because enum instances are instantiated like enumtype(*value, name=name) where value is the right side of assignment in the code RED = 'red', 'a color of blood' (in case the right side is not a tuple-like object it is wrapped into tuple beforehand)

  • protected from modifications
del LightEnum.ONE
#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#  File "fastenum.py", line 81, in __delattr__
#    self.__restrict_modification()
#  File "fastenum.py", line 69, in __restrict_modification
#    raise TypeError(f'Enum-like classes strictly prohibit changing any attribute/property after they are once set')
#TypeError: Enum-like classes strictly prohibit changing any attribute/property after they are once set
del LightEnum.ONE.name
#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#  File "fastenum.py", line 69, in __restrict_modification
#    raise TypeError(f'Enum-like classes strictly prohibit changing any attribute/property after they are once set')
#TypeError: Enum-like classes strictly prohibit changing any attribute/property after they are once set
ExtendedEnum.GREEN.description = "I've changed my mind, it's a colour of swamps"
#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#  File "fastenum.py", line 69, in __restrict_modification
#    raise TypeError(f'Enum-like classes strictly prohibit changing any attribute/property after they are once set')
#TypeError: Enum-like classes strictly prohibit changing any attribute/property after they are once set
  • protected from subclassing
class LightSub(LightEnum):
    FOUR: 'LightSub'

#Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#  File "fastenum.py", line 34, in __new__
#    typ.__call__ = typ.__new__ = typ.get
#  File "fastenum.py", line 76, in __setattr__
#    self.__restrict_modification()
#  File "fastenum.py", line 69, in __restrict_modification
#    raise TypeError(f'Enum-like classes strictly prohibit changing any attribute/property after they are once set')
#TypeError: Enum-like classes strictly prohibit changing any attribute/property after they are once set
  • but you could declare a class providing no new values (the result will be just an alias):
class LightAlias(LightEnum):
    pass

LightAlias.ONE
# <LightEnum.ONE: 1>
  • and extensible in superclasses
class ExtEnumBase(metaclass=FastEnum):
    description: Text

    __slots__ = ('description',)

    def __init__(self, value, description, name):
        self.value = value
        self.name = name
        self.description = description


class ExtEnumOne(ExtEnumBase):
    ONE = 1, 'First positive integer'
    TWO = 2, 'Second positive integer'


class ExtEnumTwo(ExtEnumBase):
    RED = 'red', 'A sunset'
    GREEN = 'green', 'Allows to cross the road'
  • as requested after publication, it's possible to subclass arbitrary classes (look at tests for more) starting from 1.3:
class IntEnum(int, metaclass=FastEnum):
    ONE = 1
    TWO = 2

# >>> IntEnum.ONE == 1
# True
# >>> IntEnum.ONE * 100
# 100

import sys
sys.exit(IntEnum.TWO)  # sets python's interpreter exit code to 2
  • faster than standard library's one
In [2]: %timeit ValuesGivenEnum.FOUR
21.4 ns ± 0.196 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: %timeit ValuesGivenEnum.FOUR.name
30.3 ns ± 0.121 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [4]: %timeit ValuesGivenEnum.FOUR.value
30.4 ns ± 0.166 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [5]: %timeit ValuesGivenEnum(4)
111 ns ± 0.599 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [6]: %timeit ValuesGivenEnum['FOUR']
84.6 ns ± 0.188 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

compare that to

# Classic enum
from enum import Enum
class StdEnum(Enum):
    ONE = 1
    TWO = 2
In [7]: %timeit StdEnum.ONE
69.2 ns ± 0.195 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [8]: %timeit StdEnum.ONE.name
247 ns ± 0.501 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [9]: %timeit StdEnum.ONE.value
249 ns ± 1.43 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [10]: %timeit StdEnum(1)
380 ns ± 3.74 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [11]: %timeit StdEnum['ONE']
134 ns ± 0.246 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

That is:

  • 3 times faster on enum's member access
  • ~8.5 times faster on enum's property (name, value) access
  • 3 times faster on enum's access by val (call on enum's class MyEnum(value))
  • 1.5 times faster on enum's access by name (dict-like MyEnum[name])

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fast-enum-1.3.0.tar.gz (9.4 kB view details)

Uploaded Source

Built Distribution

fast_enum-1.3.0-py3-none-any.whl (10.3 kB view details)

Uploaded Python 3

File details

Details for the file fast-enum-1.3.0.tar.gz.

File metadata

  • Download URL: fast-enum-1.3.0.tar.gz
  • Upload date:
  • Size: 9.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.0.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/42.0.2 requests-toolbelt/0.9.1 tqdm/4.38.0 CPython/3.7.3

File hashes

Hashes for fast-enum-1.3.0.tar.gz
Algorithm Hash digest
SHA256 4ea5b8f263eeb295bcaa6b377eec7ae1e3df20754dfbd7ead1a3aa78a8f0fc0e
MD5 e83c0e077a68524afee673cc83cf83c5
BLAKE2b-256 d6b6369ce2c8ae78f7d4b20da6e8eee484658b70a246a9172a033e71043ec2ff

See more details on using hashes here.

File details

Details for the file fast_enum-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: fast_enum-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 10.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.0.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/42.0.2 requests-toolbelt/0.9.1 tqdm/4.38.0 CPython/3.7.3

File hashes

Hashes for fast_enum-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9ea5105e7789f1b805962bdef0f232537a3b5e03d9ae46c53511ad686f0bd88d
MD5 d4d6cd5228276645b3a2ad0e7d303631
BLAKE2b-256 0d945de3a7d69130c1db32876d82cd6f90ac7f89922e9dba9e67224885f50415

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page