Можно ли переопределить __new__ в перечислении для разбора строк в экземпляр?

Я хочу разбирать строки в python перечисления. Обычно для этого используется метод анализа. Несколько дней назад я обнаружил метод __new__ , способный возвращать разные экземпляры на основе заданного параметра.

Здесь мой код, который не будет работать:

import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __new__(cls, value): if (value == "src"): return Types.Source # elif (value == "nl"): return Types.NetList # else: raise Exception() def __str__(self): if (self == Types.Unknown): return "??" elif (self == Types.Source): return "src" elif (self == Types.NetList): return "nl" 

Когда я выполняю свой скрипт Python, я получаю это сообщение:

 [...] class Types(enum.Enum): File "C:\Program Files\Python\Python 3.4.0\lib\enum.py", line 154, in __new__ enum_member._value_ = member_type(*args) TypeError: object() takes no parameters 

Как я могу вернуть правильный экземпляр значения перечисления?

Изменить 1:

Этот Enum используется при разборе URI, в частности для синтаксического анализа схемы. Таким образом, мой URI будет выглядеть следующим образом:

 nl:PoC.common.config <schema>:<namespace>[.<subnamespace>*].entity 

Поэтому после простой операции string.split я передал первую часть URI для создания перечисления.

 type = Types(splitList[0]) 

type теперь должен содержать значение перечисляемых типов с тремя возможными значениями (Unknown, Source, NetList)

Если бы я допустил псевдонимы в списке участников перечисления, невозможно будет повторить итерацию псевдонимов.

  • sys.stdout.write в python3 добавляет 11 в конец строки
  • из . import _methods ImportError: невозможно импортировать имя '_methods' в cx-freeze python
  • Скрыть некоторые возможные ошибки Pylint
  • Нет модуля с именем 'openpyxl' - Python 3.4 - Ubuntu
  • Python: Typeerror: непустая строка формата, переданная объекту .__ format__
  • Сериализация члена Enum для JSON
  • urllib HTTPS запрос: <urlopen error неизвестный тип url: https>
  • Как установить это колесо?
  • 5 Solutions collect form web for “Можно ли переопределить __new__ в перечислении для разбора строк в экземпляр?”

    Да, вы можете переопределить метод __new__() подкласса enum для реализации метода синтаксического анализа, если вы будете осторожны, но чтобы избежать указания целочисленной кодировки в двух местах, вам нужно будет определить метод отдельно, после класса, поэтому вы можете ссылаться на символические имена, определенные перечислением.

    Вот что я имею в виду:

     import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __str__(self): if (self == Types.Unknown): return "??" elif (self == Types.Source): return "src" elif (self == Types.NetList): return "nl" else: raise TypeError(self) def _Types_parser(cls, value): if not isinstance(value, str): # forward call to Types' superclass (enum.Enum) return super(Types, cls).__new__(cls, value) else: # map strings to enum values, default to Unknown return { 'nl': Types.NetList, 'ntl': Types.NetList, # alias 'src': Types.Source,}.get(value, Types.Unknown) setattr(Types, '__new__', _Types_parser) print("Types('nl') ->", Types('nl')) # Types('nl') -> nl print("Types('ntl') ->", Types('ntl')) # Types('ntl') -> nl print("Types('wtf') ->", Types('wtf')) # Types('wtf') -> ?? print("Types(1) ->", Types(1)) # Types(1) -> src 

    Обновить

    Вот более управляемая таблицами версия, которая помогает устранить некоторые повторяющиеся кодировки, которые в противном случае были бы задействованы:

     from collections import OrderedDict import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 __str__ = lambda self: Types._value_to_str.get(self) # define after Types class Types.__new__ = lambda cls, value: (cls._str_to_value.get(value, Types.Unknown) if isinstance(value, str) else super(Types, cls).__new__(cls, value)) # define look-up table and its inverse Types._str_to_value = OrderedDict((( '??', Types.Unknown), ('src', Types.Source), ('ntl', Types.NetList), # alias ( 'nl', Types.NetList),)) Types._value_to_str = {val: key for key, val in Types._str_to_value.items()} Types._str_to_value = dict(Types._str_to_value) # convert to regular dict (optional) if __name__ == '__main__': print("Types('nl') ->", Types('nl')) # Types('nl') -> nl print("Types('ntl') ->", Types('ntl')) # Types('ntl') -> nl print("Types('wtf') ->", Types('wtf')) # Types('wtf') -> ?? print("Types(1) ->", Types(1)) # Types(1) -> src print() print(list(Types)) # iterate values import pickle # demostrate picklability print(pickle.loads(pickle.dumps(Types.NetList)) == Types.NetList) # -> True 

    Метод __new__ в вашем типе enum.Enum используется для создания новых экземпляров значений перечисления, поэтому Types.Unknown , Types.Source и т. Д. Вызов enum (например, « Types('nl') обрабатывается EnumMeta.__call__ , который вы можете подкласса.

    Использование псевдонимов имен соответствует вашим требованиям

    Переопределение __call__ , возможно, __call__ для этой ситуации. Вместо этого вы можете легко использовать псевдонимы имен :

     class Types(enum.Enum): Unknown = 0 Source = 1 src = 1 NetList = 2 nl = 2 

    Здесь Types.nl является псевдонимом и возвращает тот же объект, что и Types.Netlist . Затем вы получаете доступ к элементам по именам (используя доступ к Types[..] ); поэтому Types['nl'] работает и возвращает Types.Netlist .

    Ваше утверждение о невозможности повторить итерацию псевдонимов псевдонимов неверно . Итерация явно не включает псевдонимы :

    Итерация над членами перечисления не предоставляет псевдонимов

    Псевдонимы являются частью упорядоченного словаря Enum.__members__ , если вам все еще нужен доступ к ним.

    Демонстрация:

     >>> import enum >>> class Types(enum.Enum): ... Unknown = 0 ... Source = 1 ... src = 1 ... NetList = 2 ... nl = 2 ... def __str__(self): ... if self is Types.Unknown: return '??' ... if self is Types.Source: return 'src' ... if self is Types.Netlist: return 'nl' ... >>> list(Types) [<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>] >>> list(Types.__members__) ['Unknown', 'Source', 'src', 'NetList', 'nl'] >>> Types.Source <Types.Source: 1> >>> str(Types.Source) 'src' >>> Types.src <Types.Source: 1> >>> str(Types.src) 'src' >>> Types['src'] <Types.Source: 1> >>> Types.Source is Types.src True 

    Единственное, что здесь отсутствует, это перевод неизвестных схем в Types.Unknown ; Для этого я бы использовал обработку исключений:

     try: scheme = Types[scheme] except KeyError: scheme = Types.Unknown 

    Переопределение __call__

    Если вы хотите рассматривать ваши строки как значения и использовать вызов вместо доступа к элементу, то вы переопределяете метод __call__ для метакласса:

     class TypesEnumMeta(enum.EnumMeta): def __call__(cls, value, *args, **kw): if isinstance(value, str): # map strings to enum values, defaults to Unknown value = {'nl': 2, 'src': 1}.get(value, 0) return super().__call__(value, *args, **kw) class Types(enum.Enum, metaclass=TypesEnumMeta): Unknown = 0 Source = 1 NetList = 2 

    Демо-версия:

     >>> class TypesEnumMeta(enum.EnumMeta): ... def __call__(cls, value, *args, **kw): ... if isinstance(value, str): ... value = {'nl': 2, 'src': 1}.get(value, 0) ... return super().__call__(value, *args, **kw) ... >>> class Types(enum.Enum, metaclass=TypesEnumMeta): ... Unknown = 0 ... Source = 1 ... NetList = 2 ... >>> Types('nl') <Types.NetList: 2> >>> Types('?????') <Types.Unknown: 0> 

    Обратите внимание, что здесь мы переводим строковое значение в целые числа и оставляем остальное исходной логикой Enum.

    Полностью поддерживающие псевдонимы стоимости

    Итак, enum.Enum поддерживает псевдонимы имен , вам, похоже, нужны псевдонимы значений . Переопределение __call__ может предлагать факсимиле, но мы можем сделать лучше, чем все еще, поместив определение псевдонимов значений в класс enum. Что делать, если указывать повторяющиеся имена давали вам, например, псевдонимы значений?

    Вам также необходимо предоставить подкласс enum._EnumDict как это тот класс, который запрещает повторное использование имен. Предположим, что первое значение enum является значением по умолчанию:

     class ValueAliasEnumDict(enum._EnumDict): def __init__(self): super().__init__() self._value_aliases = {} def __setitem__(self, key, value): if key in self: # register a value alias self._value_aliases[value] = self[key] else: super().__setitem__(key, value) class ValueAliasEnumMeta(enum.EnumMeta): @classmethod def __prepare__(metacls, cls, bases): return ValueAliasEnumDict() def __new__(metacls, cls, bases, classdict): enum_class = super().__new__(metacls, cls, bases, classdict) enum_class._value_aliases_ = classdict._value_aliases return enum_class def __call__(cls, value, *args, **kw): if value not in cls. _value2member_map_: value = cls._value_aliases_.get(value, next(iter(Types)).value) return super().__call__(value, *args, **kw) 

    Затем вы можете определить псевдонимы и значение по умолчанию в классе enum:

     class Types(enum.Enum, metaclass=ValueAliasEnumMeta): Unknown = 0 Source = 1 Source = 'src' NetList = 2 NetList = 'nl' 

    Демо-версия:

     >>> class Types(enum.Enum, metaclass=ValueAliasEnumMeta): ... Unknown = 0 ... Source = 1 ... Source = 'src' ... NetList = 2 ... NetList = 'nl' ... >>> Types.Source <Types.Source: 1> >>> Types('src') <Types.Source: 1> >>> Types('?????') <Types.Unknown: 0> 

    Я думаю, что самым простым решением вашей проблемы является использование функционального API класса Enum который дает больше свободы при выборе имен, поскольку мы указываем их как строки:

     from enum import Enum Types = Enum( value='Types', names=[ ('??', 0), ('Unknown', 0), ('src', 1), ('Source', 1), ('nl', 2), ('NetList', 2), ] ) 

    Это создает перечисление с псевдонимами имен. Обратите внимание на порядок записей в списке names . Первый будет выбран в качестве значения по умолчанию (а также возвращен для name ), другие – как псевдонимы, но оба могут быть использованы:

     >>> Types.src <Types.src: 1> >>> Types.Source <Types.src: 1> 

    Чтобы использовать свойство name в качестве возвращаемого значения для str(Types.src) мы заменяем версию по умолчанию из Enum :

     >>> Types.__str__ = lambda self: self.name >>> Types.__format__ = lambda self, _: self.name >>> str(Types.Unknown) '??' >>> '{}'.format(Types.Source) 'src' >>> Types['src'] <Types.src: 1> 

    Обратите внимание, что мы также заменяем метод __format__ который вызывается str.format() .

    Возможно ли переопределить __new__ в перечислении python для синтаксического анализа строк для экземпляра?

    Одним словом, да. Как показывает Мартино, вы можете заменить метод __new__ после того, как класс был инстанцирован (его исходный код):

     class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __str__(self): if (self == Types.Unknown): return "??" elif (self == Types.Source): return "src" elif (self == Types.NetList): return "nl" else: raise TypeError(self) # completely unnecessary def _Types_parser(cls, value): if not isinstance(value, str): raise TypeError(value) else: # map strings to enum values, default to Unknown return { 'nl': Types.NetList, 'ntl': Types.NetList, # alias 'src': Types.Source,}.get(value, Types.Unknown) setattr(Types, '__new__', _Types_parser) 

    а также, как показывает его демонстрационный код, если вы не очень осторожны, вы нарушите другие вещи, такие как травление и даже базовый поиск по элементам:

     --> print("Types(1) ->", Types(1)) # doesn't work Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.4/enum.py", line 219, in __call__ return cls.__new__(cls, value) File "<stdin>", line 3, in _Types_parser TypeError: 1 --> import pickle --> pickle.loads(pickle.dumps(Types.NetList)) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in _Types_parser TypeError: 2 

    Martijn показал, что это умный способ EnumMeta чтобы получить то, что мы хотим:

     class TypesEnumMeta(enum.EnumMeta): def __call__(cls, value, *args, **kw): if isinstance(value, str): # map strings to enum values, defaults to Unknown value = {'nl': 2, 'src': 1}.get(value, 0) return super().__call__(value, *args, **kw) class Types(enum.Enum, metaclass=TypesEnumMeta): ... 

    но это ставит нас с дублирующимся кодом и работает против типа Enum.

    Единственное, чего не хватает в базовой поддержке Enum для вашего прецедента, – это возможность иметь один член по умолчанию, но даже это можно корректно обработать в обычном подклассе Enum , создав новый метод класса.

    Класс, который вы хотите:

     class Types(enum.Enum): Unknown = 0 Source = 1 src = 1 NetList = 2 nl = 2 def __str__(self): if self is Types.Unknown: return "??" elif self is Types.Source: return "src" elif self is Types.NetList: return "nl" @classmethod def get(cls, name): try: return cls[name] except KeyError: return cls.Unknown 

    и в действии:

     --> for obj in Types: ... print(obj) ... ?? src nl --> Types.get('PoC') <Types.Unknown: 0> 

    Если вам действительно нужны псевдонимы значений, даже это можно обработать, не прибегая к метаклассическому взлому:

     class Types(Enum): Unknown = 0, Source = 1, 'src' NetList = 2, 'nl' def __new__(cls, int_value, *value_aliases): obj = object.__new__(cls) obj._value_ = int_value for alias in value_aliases: cls._value2member_map_[alias] = obj return obj print(list(Types)) print(Types(1)) print(Types('src')) 

    который дает нам:

     [<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>] Types.Source Types.Source 

    У меня недостаточно комментариев, чтобы прокомментировать принятый ответ, но в Python 2.7 с пакетом enum34 во время выполнения появляется следующая ошибка:

    «unbound method <lambda> () должен быть вызван с экземпляром MyEnum в качестве первого аргумента (вместо него был экземпляр EnumMeta)"

    Я смог исправить это, изменив:

     # define after Types class Types.__new__ = lambda cls, value: (cls._str_to_value.get(value, Types.Unknown) if isinstance(value, str) else super(Types, cls).__new__(cls, value)) 

    к следующему, обертывание лямбда в staticmethod ():

     # define after Types class Types.__new__ = staticmethod( lambda cls, value: (cls._str_to_value.get(value, Types.Unknown) if isinstance(value, str) else super(Types, cls).__new__(cls, value))) 

    Этот код проверен правильно как в Python 2.7, так и в 3.6.

    Python - лучший язык программирования в мире.