Can I create an enum class RockPaperScissors
such that ROCK.value == "rock"
and ROCK.beats == SCISSORS
, where ROCK
and SCISSORS
are both constants in RockPaperScissors
?

- 55,365
- 30
- 138
- 223
-
Why not a regular class with attribute value and beats? – Devesh Kumar Singh Jun 04 '19 at 04:58
-
2@DeveshKumarSingh Because an `Enum` nicely encodes the semantics of a collection of related constants rather than an arbitrary class. That said, would it be much simpler as a regular class? – l0b0 Jun 04 '19 at 05:01
6 Answers
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]

- 338,267
- 99
- 616
- 750
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

- 21,584
- 4
- 41
- 73
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.

- 63,992
- 20
- 159
- 237
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

- 2,324
- 3
- 22
- 27
-
I like the idea of using `__ge__`, but Rock > Scissor should be true, right? – wim Jun 04 '19 at 05:37
-
Thanks, fixed. Needed to use both __ gt__ and __ lt__ to have a complete ordering. – Eran Jun 04 '19 at 06:11
-
You should also add an instance check to make sure you're comparing apples with apples (returning `NotImplemented` otherwise) – wim Jun 04 '19 at 06:13
-
-
Your `Rock` and `Paper` members need to lose the comma in their definition as that makes their values be tuples instead of integers. – Ethan Furman Nov 07 '19 at 17:00
-
@EthanFurman, thanks for your comment, however in this case the comma doesn't indicate a single-tuple. It's true that the comma is redundant, and that it has no effect and can be removed. Note that even with the comma: print(type(RPS.Rock.value)) will print
and print(RPS.Rock.value) will print 1. Also because the inherited type is IntEnum anything that isn't an int (or convertible to it using int() ) will cause a ValueError when the Enum is parsed. – Eran Nov 07 '19 at 20:17 -
Ah, right you are. The comma does, in fact, create a single-item tuple, but then that tuple is passed to `int()` where it is correctly converted. It is still a bad habit to have, though, as most other types of `Enum` will end up with a tuple as the value. – Ethan Furman Nov 07 '19 at 20:41
-
@EthanFurman, you are right that it's a bad habit, I will change the answer. However, `int((1,))` throws a TypeError, so something still doesn't sit right with your explanation. – Eran Nov 07 '19 at 20:55
-
1The way the `value` is passed to a mixed-Enum is `obj = __new__(*value)` so a 1-item tuple becomes `obj = __new__(1)`. Interestingly, you could say `ROCK = 'A', 16` which would become `obj = __new__(*('A', 16))` which becomes `__new__('A', 16)` which becomes `ROCK.value == 10`. Thank you for making me get my story straight. :) – Ethan Furman Nov 07 '19 at 23:30
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

- 50,409
- 9
- 43
- 69
-
1Can I declare the `beats` property *within* the class, or do I have to do it outside? – l0b0 Jun 04 '19 at 05:03
-
@l0b0 I guess one could try to do something with decorators - not sure and no time to fiddle right now - maybe look into [this](https://stackoverflow.com/a/1594484/7505395) – Patrick Artner Jun 04 '19 at 05:26
-
1
-
1Googling "flansh" doesn't seem to bring up any pertinent explanation. Apparently this is a German slang word? – tripleee Jun 04 '19 at 06:11
-
1@trip who would have thought. It might be german - I was under the impression it has something to do attatching somthing to some other thing: [here (german)](https://de.wikipedia.org/wiki/Flansch) - in english it would be [flange](https://www.dict.cc/englisch-deutsch/to+flange+sth.html). Reworded answer. – Patrick Artner Jun 04 '19 at 06:21
-
2One issue with this approach is that the instance dict just remains writeable- any other code can put RPS.Rock.beats = "potato". If you using property/descriptors then you get a read-only attribute. – wim Jun 04 '19 at 07:02
-
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())

- 4,721
- 2
- 18
- 27