简体   繁体   中英

What is the easiest way to make ByteEnum in Python?

What I need

I am writing a program where I want to define enum with possible interpretation of certain value that is stored one byte. So it is more specific than IntEnum as my value must be int in range 0x00-0xFF.

There are two types of values in the enum:

  • specified by the standard (those I define on my own eg 0x00-0x7F)
  • user specific (I let the User to define those, eg 0x80-0xFF)

Therefore, I am using aenum instead of classic enum package as my enum might register new members after the program is started.

What is desired solution

I would like to add some code to IntEnum to restrict possible member values, so it has to be both int and 0x00 <= value <= 0xFF or some exception would be raised.
Why? To stop user from defining improper values (eg -1 , 256 , 10.0 ) that are not int or not in range 0x00-0xFF.

What are my ideas

I thought to update __new__ method of IntEnum , but its nearly 500 lines long and it is rather hard to understand that. Though, I have notice it has boundary parameter. Isn't what I need (lower boundar = 0x00, higher bondary = 0xFF)?

My code looks like this now

from aenum import IntEnum, unique

@unique
class SomeByteEnum(IntEnum):
    FOO = 1
    BAR = 2
    ...

After the update, following cases shall raise an exception:

@unique
class SomeByteEnum(IntEnum):
    ...  # some magical code here

    INVALID_VALUE = -1  # value is lower than minimal byte value 0x00, exception shall be raised
from aenum import extend_enum

extend_enum(SomeByteEnum, "INVALID_VALUE", 0x100) # value is greater than max byte value 0xFF, exception shall be raised

Enum members are created when the class is created, so your

SomeByteEnum.INVALID_VALUE = 0x100

is not creating a new member, just an ordinary attribute. In others words:

>>> isinstance(SomeByteEnum.FOO, SomeByteEnum)
True

>>> isinstance(SomeByteEnum.INVALIDVALUE, SomeByteEnum)
False

It is possible to have both standard name/values and custom name/values:

class SomeByteEnum(IntEnum):
    StdValue1 = 0x01
    StdValue2 = 0x02
    ...
    locals().update(get_user_name_value_pairs())

To implement your value restrictions you'll need to write your own __new__ :

    def __new__(cls, value):
        if value < 0x00 or value > 0xff:
            raise ValueError('value must be between 0x00 and 0xff [got %r]' % (value, ))
        member = int.__new__(cls, value)
        member._value_ = value
        return member

Note: the above is untested and may contain minor errors.

Wrt:

I thought to update __new__ method of IntEnum , but its nearly 500 lines long and it is rather hard to understand that.

You will have to update __new__ but of your own enum class, not the actual IntEnum .

This is what worked, notes below:

import aenum

@aenum.unique
class SomeByteEnum(aenum.IntEnum):
    def __new__(cls, value):
        if not 0 <= value <= 255:
            raise ValueError('Value must be >=0 and <= 255')
        if not isinstance(value, int):
            raise TypeError('Value must be an int')
        obj = int.__new__(cls, value)
        obj._value_ = value
        return obj
    
    ZEROTH = 0
    FIRST = 1
    SECOND = 2
    THIRD = 3

Usage:

>>> from aenum import extend_enum
>>>
>>> list(SomeByteEnum)
[<SomeByteEnum.ZEROTH: 0>, <SomeByteEnum.FIRST: 1>, <SomeByteEnum.SECOND: 2>,
 <SomeByteEnum.THIRD: 3>]
>>>
>>> # allowed:
>>> extend_enum(SomeByteEnum, 'FOURTH', 4)
>>> list(SomeByteEnum)
[<SomeByteEnum.ZEROTH: 0>, <SomeByteEnum.FIRST: 1>, <SomeByteEnum.SECOND: 2>,
 <SomeByteEnum.THIRD: 3>, <SomeByteEnum.FOURTH: 4>]
>>>
>>> # NOT allowed:
>>> extend_enum(SomeByteEnum, 'MeeeeeLION', 1_000_000)
Traceback (most recent call last):
  ...
ValueError: Value must be >=0 and <= 255
>>>
>>> # NOT allowed:
>>> extend_enum(SomeByteEnum, 'SIXFEETUNDER', -6)
Traceback (most recent call last):
  ...
ValueError: Value must be >=0 and <= 255
>>>
>>> # floats NOT allowed:
>>> extend_enum(SomeByteEnum, '5 point 5', 5.5)
Traceback (most recent call last):
  ...
TypeError: Value must be an int
>>>
>>> extend_enum(SomeByteEnum, 'TooTRUE', True)  # True = 1 so is absorbed, no error
cls=<aenum 'SomeByteEnum'> value=True
>>>
>>> # check members
>>> for m in SomeByteEnum:
...     print(m.name, m.value)
...
ZEROTH 0
FIRST 1
SECOND 2
THIRD 3
FOURTH 4
>>>

Notes:

  1. Specifying a start=0 like class SomeByteEnum(aenum.IntEnum, start=0): breaks how value works - extended members cannot be added.
  2. Used aenum=2.2.6 , had some trouble with 3.0.0, like for m in SomeByteEnum not showing the added members.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM