Enum Class метод со значением Enum по умолчанию не работает - PullRequest
2 голосов
/ 07 ноября 2019

Мне хорошо известно, что если у вас есть метод класса, который использует имя класса enum для намека на тип, есть способ заставить его работать на Python 3.6 и ниже.

Вместо ...

class Release(Enum):
   ...
   @classmethod
   def get(cls, release: Release):
      ...

Вам нужно использовать строковое значение примерно так ...

class Release(Enum):
   ...
   @classmethod
   def get(cls, release: "Release"):
      ...

Я верю, что в Python 3.7 и выше есть питонский способ обойти это "хак", где вы надеваетене нужно использовать кавычки. Причина в том, что «класс еще не существует, пока все методы и переменные не будут сделаны первыми». Так как класс еще не существует, я пока не могу использовать имя класса и должен использовать строку в кавычках в качестве хака.

Однако я пытаюсь пойти еще дальше ииспользуйте значение по умолчанию. И это не работает. Есть ли в Python 3.6 подход, который не является хаком? Кроме того, есть ли исправление в Python 3.7 и выше?

Код

from enum import Enum

class Release(Enum):
    Canary = (1, [])
    Beta = (2, [1])
    RC = (3, [2, 1])
    Stable = (4, [3, 2, 1])

    def __new__(cls, value, cascade):
        obj = object.__new__(cls)
        obj._value_ = value
        obj.current = ["Release" * value] # This would technically be a list of all releasese in this enum. This is just to emulate different values
        obj.cascade = cascade
        return obj

    @classmethod
    def get_all_releases(cls, release: "Release" = Canary):  # Default Value = Release.Canary
        return release.current


print(Release.get_all_releases(Release.Canary))
print(Release.get_all_releases(Release.Beta))
print(Release.get_all_releases(Release.RC))
print(Release.get_all_releases(Release.Stable))

# Error. Even with default value
# print(Release.get_all_releases())

С этим кодом Iполучите следующее сообщение об ошибке

AttributeError: 'tuple' object has no attribute 'current'

Это потому, что он возвращает кортеж Canary вместо фактического значения.

Ответы [ 3 ]

1 голос
/ 07 ноября 2019

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

    def __new__(cls, value, cascade):
        obj = object.__new__(cls)
        obj._value_ = value
        obj.current = ["Release" * value]      # not sure what this should actually be

        # if always the previous versions (don't need cascade defined)
        obj.cascade = sorted(list(cls), reverse=True)

        # if some already defined subset (need cascade defined)
        obj.cascade = [cls._value2member_map_(c) for c in cascade]

        return obj

Вторая техникаможет идти двумя путями - по умолчанию всегда первый Enum член:

    @classmethod
    def get_all_releases(cls):
        return list(cls[0]).current

или, если по умолчанию может быть любой член, то что-то похожее на этот ответ должно работать:

class add_default:
    """
    add DEFAULT psuedo-member to enumeration; use first member if none specified
    (default should be name of member)
    """
    def __init__(self, default=''):
        self._default = default
    def __call__(self, enumeration):
        if self._default:
            member = enumeration[self._default]
        else:
            member = enumeration[enumeration._member_names_[0]]
        enumeration._member_map_['DEFAULT'] = member
        return enumeration

Ваш окончательный Enum будет выглядеть так (при условии, что cascade - все предыдущие члены и использует подход декоратора):

@add_default('Canary')
class Release(Enum):
    Canary = 1
    Beta = 2
    RC = 3
    Stable = 4
    def __new__(cls, value):
        obj = object.__new__(cls)
        obj._value_ = value
        obj.current = ["Release" * value]      # not sure what this should actually be or how it's calculated
        obj.cascade = list(cls)[::-1]
        return obj
    @classmethod
    def get_all_releases(cls, release: "Release" = None):
        if release is None:
            release = cls.DEFAULT
        return release.current

и используется:

>>> Release.DEFAULT
<Release.Canary: 1>

>>> Release.get_all_releases()
['Release']

>>> Release.get_all_releases(Release.RC)
['ReleaseReleaseRelease']

Оригинальный ответ

Проблема, с которой вы столкнулись с вашим кодом, здесь:

class Release(Enum):
    Canary = 1,

Включив эту дополнительную запятую, высделали значение для Canary быть (1, ). Удалите эту запятую, чтобы избавиться от исключения tuple.

1 голос
/ 07 ноября 2019

Получил подсказку от @ufoxDan с его ответом, но попытался сделать его менее обходным и более естественным.

По сути, я начал с проверки type(release) до return и заметил, чтоЯ получил результаты ..

<enum 'Release'>
<enum 'Release'>
<enum 'Release'>
<enum 'Release'>
<class 'tuple'>

Я заметил, что если тип был Release, тогда я мог бы просто выполнить код, однако, если бы он был что-нибудь еще, например None вместо несозданного Canary типа, тогда я мог бы предположить, что он запрашивает Canary. Поэтому я сделал следующее ...

@classmethod
def get_all_releases(cls, release: "Release" = None):
   if type(release) is Release:
       return release.current
   return Release.Canary.current

# Now these all work
print(Release.get_all_releases())
print(Release.get_all_releases(Release.Canary))
print(Release.get_all_releases(Release.Stable))

Похоже, это самый питонический способ достижения результатов. Это, кажется, также лучший способ при чтении кода и без повторения кода. Любой должен иметь возможность реализовать нечто похожее, как кажется.

1 голос
/ 07 ноября 2019

Хотя это определенно обходной путь, мне показалось, что это хорошо работает:

@classmethod
def get_all_releases(cls, release: "Release" = Canary):  # Default Value = Release.Canary
    if release == (Release.Canary.value,):
        return Release.Canary.current
    return release.current

Он работает для любого значения, которое вы присваиваете Canary. Так что, пока это ваш стандарт, я верю, что это сработает.


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

class Release(Enum):
    Canary = 6,
    Beta = 2,
    RC = 3,
    Stable = 4
    default = Canary

    ...

    @classmethod
    def get_all_releases(cls, release: "Release" = default):
        if release == (Release.Canary.value,):
            return Release.Canary.current
        return release.current
...