How to create a self-referential Python 3 Enum?

谁说胖子不能爱 提交于 2019-12-05 00:28:40

Enum members are instances of the type. This means you can just use a regular property:

from enum import Enum

class RockPaperScissors(Enum):
    Rock = "rock"
    Paper = "paper"
    Scissors = "scissors"

    @property
    def beats(self):
        lookup = {
            RockPaperScissors.Rock: RockPaperScissors.Scissors,
            RockPaperScissors.Scissors: RockPaperScissors.Paper,
            RockPaperScissors.Paper: RockPaperScissors.Rock,
        }
        return lookup[self]

By picking the order of the members carefully, each member can simply be described as beating the previous with a property.

from enum import Enum

class RPS(Enum):
    Rock = 0
    Paper = 1
    Scissor = 2

    @property
    def beats(self):
        return list(RPS)[self.value - 1]

for v in RPS:
    print(v.name, 'beats', v.beats.name)

Output

Rock beats Scissor
Paper beats Rock
Scissor beats Paper

Having Enum members refer to each other during class creation is a bit tricky; the trick is knowing that each member is created and initialized before it is added to the Enum itself. This means you can examine the state of the Enum you are creating and make adjustments to both the member-being-created as well as members-already-created.

The basic problem here is making circular references between the members, and we can solve that by modifying the circle with each new member:

class RPS(Enum):

    Rock = "rock"
    Paper = "paper"
    Scissors = "scissors"

    def __init__(self, value):
        if len(self.__class__):
            # make links
            all = list(self.__class__)
            first, previous = all[0], all[-1]
            first.beats = self
            self.beats = previous

and in use:

>>> print(RPS.Rock.beats)
RPS.Scissors

>>> print(RPS.Paper.beats)
RPS.Rock

>>> print(RPS.Scissors.beats)
RPS.Paper

Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

What about something like this:

from enum import IntEnum

class RPS(IntEnum):
    Rock = 1
    Paper = 2
    Scissor = 3

    def __lt__(self, other):
        if self == RPS.Scissor and other == RPS.Rock:
            return True
        if self == RPS.Rock and other == RPS.Scissor:
            return False
        return self.value < other.value

    def __gt__(self, other):
        if self == RPS.Rock and other == RPS.Scissor:
            return True
        if self == RPS.Scissor and other == RPS.Rock:
            return False
        return self.value > other.value

It's not Rock.beats, but it seems more logical for who beats who to be on the enum (or the class), it's not an inherit property of Rock to beat Scissor, it's how we define RPS (could have just as well be the other way around if you decided to try something else) And with the python method ge (and you can implement the rest if needed) you can get comparison naturally looking like this:

from itertools import combinations
members = list(RPS)
for pair in combinations(members, 2):
    print(f'{pair[1].name} < {pair[0].name} ? {pair[1] < pair[0]}')
    print(f'{pair[0].name} < {pair[1].name} ? {pair[0] < pair[1]}')

    print(f'{pair[1].name} > {pair[0].name} ? {pair[1] > pair[0]}')
    print(f'{pair[0].name} > {pair[1].name} ? {pair[0] > pair[1]}')

which outputs:

Paper < Rock ? False
Rock < Paper ? True
Paper > Rock ? True
Rock > Paper ? False
Scissor < Rock ? True
Rock < Scissor ? False
Scissor > Rock ? False
Rock > Scissor ? True
Scissor < Paper ? False
Paper < Scissor ? True
Scissor > Paper ? True
Paper > Scissor ? False

For completeness sake, it could be done like this as well. It's more descriptive, but requires this getattr call, which I'm not the biggest fan of, personally.

from enum import Enum

class RockPaperScissors(Enum):

    ROCK = ("rock", "scissors")
    PAPER = ("paper", "rock")
    SCISSORS = ("scissors", "paper")

    def __init__(self, value, beats):
        self._value_ = value
        self._beats = beats

    @property
    def beats(self):
        return getattr(RockPaperScissors, self._beats.upper())

Did you try?

from enum import IntEnum

class RPS(IntEnum):
    Rock = 1 
    Paper = 2 
    Scissor = 3

RPS.Rock.beats = RPS.Scissor
RPS.Paper.beats = RPS.Rock
RPS.Scissor.beats = RPS.Paper

for i in [RPS.Rock,RPS.Paper,RPS.Scissor]:
    print(i, "beats", i.beats)

Output:

RPS.Rock beats RPS.Scissor
RPS.Paper beats RPS.Rock
RPS.Scissor beats RPS.Paper

Yes. You can.


In python all (*) things are objects and you can attach further properties onto them:

def func():
    pass

func.result = 42

print(func.result)  # 42

*) few exceptions apply

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!