Una forma más python de definir una enumeración con miembros dynamics

Necesitaba crear una enumeración para representar los códigos de país ISO. Los datos del código de país provienen de un archivo json que se puede obtener en: https://github.com/lukes/ISO-3166-Countries-with-Regional-Codes

Así que lo que he hecho es:

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]) 

Este fragmento de código es francamente feo. Busqué formas alternativas de definir la clase de enumeración, pero no pude armar una solución. ¿Hay una manera de definir la enumeración en la siguiente forma:

 class CountryCode(enum.Enum): data = json.load(open('slim-2.json')) # Some code to define the enum members @classmethod def choices(cls): # etc... 

¿Alguna sugerencia sobre cómo hacer esto?

¿Qué tal esto?

 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) 
  • Reemplazó [...data[i]... for i in range(len(data))] con [...x... for x in data] ; Puede itearte la secuencia (lista, data en el código) sin usar índices.
  • Utiliza CountryCode.attr = ... consistentemente; en lugar de mezclar CountryCode.attr = ... y setattr(CountryCode, 'attr', ...) .

Actualización Para obtener una solución general de json-to-Enum Cuándo debo subclasificar EnumMeta en lugar de Enum? .


Parece que está intentando realizar un seguimiento de tres datos:

  • nombre del país
  • código de país
  • país abreviatura de 2 letras

Debería considerar el uso de una técnica inspirada en un mixin namedtuple como se ilustra en esta respuesta :


La forma estándar

Necesitaremos una clase base para mantener el comportamiento:

 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() 

Luego podemos usar eso para crear la clase de Country real:

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

El camino aenum 1 2

 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 

Si bien incluí el método de choices , puede que no lo necesites:

 >>> Country('AF')  >>> Country(4)  >>> Country('Afghanistan')  

1 Divulgación: Soy el autor de Python stdlib Enum , el enum34 backport y la biblioteca de enumeración avanzada ( aenum ) .

2 Esto requiere aenum 2.0.5+ .

Sí, hay una manera de definir la enum utilizando la syntax de statement alternativa que desee. Funciona ocultando el código “feo” en una metaclase derivada de enum.EnumMeta . Si lo desea, también sería posible definir el método de clase choices() allí.

 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())