关于python:一种更加pythonic的方式来定义动态成员的枚举

A more pythonic way to define an enum with dynamic members

我需要创建一个枚举来表示ISO国家代码。国家代码数据来自JSON文件,可从以下网址获取:https://github.com/lukes/iso-3166-countries-with-regional-codes

所以我所做的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
data = json.load(open('slim-2.json'))
codes_list = [(data[i]['alpha-2'], int(data[i]['country-code']))
              for i in range(len(data))]

CountryCode = enum.Enum('CountryCode', codes_list,)

names_dict = {int(data[i]['country-code']):data[i]['name']
              for i in range(len(data))}
setattr(CountryCode, '_names', names_dict)

CountryCode.choices = classmethod(lambda cls:((member.value, name)
                                  for name, member in cls.__members__.items()))
setattr(CountryCode, '__str__' ,lambda self: self.__class__._names[self.value])

坦率地说,这段代码很难看。我研究了定义枚举类的其他方法,但无法将解决方案组合在一起。是否可以用以下形式定义枚举:

1
2
3
4
5
6
7
8
class CountryCode(enum.Enum):

    data = json.load(open('slim-2.json'))
    # Some code to define the enum members

    @classmethod
    def choices(cls):
    # etc...

有什么建议吗?


通用json-to-Enum解决方案的更新签出何时应将Enummeta子类而不是Enum子类?.

看起来您试图跟踪三个数据:

  • 国家名称
  • 国家代码
  • 国家2字母缩写
  • 小精灵

    您应该考虑使用受namedtuple混音启发的技术,如本答案所示:

    stdlib方式

    我们需要一个基类来保存该行为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from enum import Enum
    import json

    class BaseCountry(Enum):

        def __new__(cls, record):
            member = object.__new__(cls)
            member.country_name = record['name']
            member.code = int(record['country-code'])
            member.abbr = record['alpha-2']
            member._value_ = member.abbr, member.code, member.country_name
            if not hasattr(cls, '_choices'):
                cls._choices = {}
            cls._choices[member.code] = member.country_name
            cls._choices[member.abbr] = member.country_name
            return member                

        def __str__(self):
            return self.country_name

        @classmethod
        def choices(cls):
            return cls._choices.copy()

    然后我们可以使用它来创建实际的Country类:

    1
    2
    3
    4
    Country = BaseCountry(
            'Country',
            [(rec['alpha-2'], rec) for rec in json.load(open('slim-2.json'))],
            )

    aenum路12

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    from aenum import Enum, MultiValue
    import json

    class Country(Enum, init='abbr code country_name', settings=MultiValue):

        _ignore_ = 'this country'  # do not add these names as members

        # create members
        this = vars()
        for country in json.load(open('slim-2.json')):
            this[country['alpha-2']] = (
                    country['alpha-2'],
                    int(country['country-code']),
                    country['name'],
                    )

        # return a dict of choices by abbr or country code to name
        @classmethod
        def choices(cls):
            mapping = {}
            for member in cls:
                mapping[member.code] = member.name
                mapping[member.abbr] = member.name
            return mapping

        # have str() print just the country name
        def __str__(self):
            return self.country_name

    虽然我包含了choices方法,但您可能不需要它:

    1
    2
    3
    4
    5
    6
    7
    8
    >>> Country('AF')
    <Country.AF: ('AF', 4, 'Afghanistan')>

    >>> Country(4)
    <Country.AF: ('AF', 4, 'Afghanistan')>

    >>> Country('Afghanistan')
    <Country.AF: ('AF', 4, 'Afghanistan')>

    1公开:我是python stdlib enumenum34backport和advanced enumeration(aenum库的作者。

    2这就需要aenum 2.0.5+


    是的,有一种方法可以使用您想要的替代声明语法来定义enum。它的工作原理是将"丑陋的"代码隐藏在从enum.EnumMeta派生的元类中。如果您愿意,也可以在那里定义choices()类方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import enum
    import json

    class CountryCodeMeta(enum.EnumMeta):
        def __new__(metacls, cls, bases, classdict):
            data = classdict['data']
            names = [(country['alpha-2'], int(country['country-code'])) for country in data]

            temp = type(classdict)()
            for name, value in names:
                temp[name] = value

            excluded = set(temp) | set(('data',))
            temp.update(item for item in classdict.items() if item[0] not in excluded)

            return super(CountryCodeMeta, metacls).__new__(metacls, cls, bases, temp)

    class CountryCode(enum.Enum, metaclass=CountryCodeMeta):
        data = json.load(open('slim-2.json'))

        @classmethod
        def choices(cls):
            return ((member.value, name) for name, member in cls.__members__.items())


    这个怎么样?

    1
    2
    3
    4
    5
    6
    7
    data = json.load(open('slim-2.json'))
    CountryCode = enum.Enum('CountryCode', [
        (x['alpha-2'], int(x['country-code'])) for x in data
    ])
    CountryCode._names = {x['alpha-2']: x['name'] for x in data}
    CountryCode.__str__ = lambda self: self._names[self.name]
    CountryCode.choices = lambda: ((e.value, e.name) for e in CountryCode)
    • [...x... for x in data]替换[...data[i]... for i in range(len(data))];不使用索引就可以生成一个序列(列表,代码中的data)。
    • 一直使用CountryCode.attr = ...;而不是混合CountryCode.attr = ...setattr(CountryCode, 'attr', ...)
    • 小精灵