Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
5bdab641
Commit
5bdab641
authored
Sep 21, 2018
by
Ethan Furman
Committed by
GitHub
Sep 21, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
bpo-29577: Enum: mixin classes don't mix well with already mixed Enums (GH-9328)
* bpo-29577: allow multiple mixin classes
parent
fd97d1f1
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
229 additions
and
34 deletions
+229
-34
Doc/library/enum.rst
Doc/library/enum.rst
+10
-3
Lib/enum.py
Lib/enum.py
+19
-31
Lib/test/test_enum.py
Lib/test/test_enum.py
+199
-0
Misc/NEWS.d/next/Library/2018-09-14-20-00-47.bpo-29577.RzwKFD.rst
...S.d/next/Library/2018-09-14-20-00-47.bpo-29577.RzwKFD.rst
+1
-0
No files found.
Doc/library/enum.rst
View file @
5bdab641
...
...
@@ -387,10 +387,17 @@ whatever value(s) were given to the enum member will be passed into those
methods. See `Planet`_ for an example.
Restricted
subclassing of enumerations
---------------------------
-----------
Restricted
Enum subclassing
---------------------------
Subclassing an enumeration is allowed only if the enumeration does not define
A new :class:`Enum` class must have one base Enum class, up to one concrete
data type, and as many :class:`object`-based mixin classes as needed. The
order of these base classes is::
def EnumName([mix-in, ...,] [data-type,] base-enum):
pass
Also, subclassing an enumeration is allowed only if the enumeration does not define
any members. So this is forbidden::
>>> class MoreColor(Color):
...
...
Lib/enum.py
View file @
5bdab641
...
...
@@ -480,37 +480,25 @@ class EnumMeta(type):
if
not
bases
:
return
object
,
Enum
# double check that we are not subclassing a class with existing
# enumeration members; while we're at it, see if any other data
# type has been mixed in so we can use the correct __new__
member_type
=
first_enum
=
None
for
base
in
bases
:
if
(
base
is
not
Enum
and
issubclass
(
base
,
Enum
)
and
base
.
_member_names_
):
raise
TypeError
(
"Cannot extend enumerations"
)
# base is now the last base in bases
if
not
issubclass
(
base
,
Enum
):
raise
TypeError
(
"new enumerations must be created as "
"`ClassName([mixin_type,] enum_type)`"
)
# get correct mix-in type (either mix-in type of Enum subclass, or
# first base if last base is Enum)
if
not
issubclass
(
bases
[
0
],
Enum
):
member_type
=
bases
[
0
]
# first data type
first_enum
=
bases
[
-
1
]
# enum type
else
:
for
base
in
bases
[
0
].
__mro__
:
# most common: (IntEnum, int, Enum, object)
# possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
# <class 'int'>, <Enum 'Enum'>,
# <class 'object'>)
if
issubclass
(
base
,
Enum
):
if
first_enum
is
None
:
first_enum
=
base
else
:
if
member_type
is
None
:
member_type
=
base
def
_find_data_type
(
bases
):
for
chain
in
bases
:
for
base
in
chain
.
__mro__
:
if
base
is
object
:
continue
elif
'__new__'
in
base
.
__dict__
:
if
issubclass
(
base
,
Enum
)
and
not
hasattr
(
base
,
'__new_member__'
):
continue
return
base
# ensure final parent class is an Enum derivative, find any concrete
# data type, and check that Enum has no members
first_enum
=
bases
[
-
1
]
if
not
issubclass
(
first_enum
,
Enum
):
raise
TypeError
(
"new enumerations should be created as "
"`EnumName([mixin_type, ...] [data_type,] enum_type)`"
)
member_type
=
_find_data_type
(
bases
)
or
object
if
first_enum
.
_member_names_
:
raise
TypeError
(
"Cannot extend enumerations"
)
return
member_type
,
first_enum
...
...
Lib/test/test_enum.py
View file @
5bdab641
...
...
@@ -122,6 +122,22 @@ class TestHelpers(unittest.TestCase):
'__'
,
'___'
,
'____'
,
'_____'
,):
self
.
assertFalse
(
enum
.
_is_dunder
(
s
))
# for subclassing tests
class
classproperty
:
def
__init__
(
self
,
fget
=
None
,
fset
=
None
,
fdel
=
None
,
doc
=
None
):
self
.
fget
=
fget
self
.
fset
=
fset
self
.
fdel
=
fdel
if
doc
is
None
and
fget
is
not
None
:
doc
=
fget
.
__doc__
self
.
__doc__
=
doc
def
__get__
(
self
,
instance
,
ownerclass
):
return
self
.
fget
(
ownerclass
)
# tests
class
TestEnum
(
unittest
.
TestCase
):
...
...
@@ -1730,6 +1746,102 @@ class TestEnum(unittest.TestCase):
else
:
raise
Exception
(
'Exception not raised.'
)
def
test_multiple_mixin
(
self
):
class
MaxMixin
:
@
classproperty
def
MAX
(
cls
):
max
=
len
(
cls
)
cls
.
MAX
=
max
return
max
class
StrMixin
:
def
__str__
(
self
):
return
self
.
_name_
.
lower
()
class
SomeEnum
(
Enum
):
def
behavior
(
self
):
return
'booyah'
class
AnotherEnum
(
Enum
):
def
behavior
(
self
):
return
'nuhuh!'
def
social
(
self
):
return
"what's up?"
class
Color
(
MaxMixin
,
Enum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
Color
.
MAX
,
3
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'Color.BLUE'
)
class
Color
(
MaxMixin
,
StrMixin
,
Enum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
Color
.
MAX
,
3
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'blue'
)
class
Color
(
StrMixin
,
MaxMixin
,
Enum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
Color
.
MAX
,
3
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'blue'
)
class
CoolColor
(
StrMixin
,
SomeEnum
,
Enum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
CoolColor
.
RED
.
value
,
1
)
self
.
assertEqual
(
CoolColor
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
CoolColor
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
str
(
CoolColor
.
BLUE
),
'blue'
)
self
.
assertEqual
(
CoolColor
.
RED
.
behavior
(),
'booyah'
)
class
CoolerColor
(
StrMixin
,
AnotherEnum
,
Enum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
CoolerColor
.
RED
.
value
,
1
)
self
.
assertEqual
(
CoolerColor
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
CoolerColor
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
str
(
CoolerColor
.
BLUE
),
'blue'
)
self
.
assertEqual
(
CoolerColor
.
RED
.
behavior
(),
'nuhuh!'
)
self
.
assertEqual
(
CoolerColor
.
RED
.
social
(),
"what's up?"
)
class
CoolestColor
(
StrMixin
,
SomeEnum
,
AnotherEnum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
CoolestColor
.
RED
.
value
,
1
)
self
.
assertEqual
(
CoolestColor
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
CoolestColor
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
str
(
CoolestColor
.
BLUE
),
'blue'
)
self
.
assertEqual
(
CoolestColor
.
RED
.
behavior
(),
'booyah'
)
self
.
assertEqual
(
CoolestColor
.
RED
.
social
(),
"what's up?"
)
class
ConfusedColor
(
StrMixin
,
AnotherEnum
,
SomeEnum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
ConfusedColor
.
RED
.
value
,
1
)
self
.
assertEqual
(
ConfusedColor
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
ConfusedColor
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
str
(
ConfusedColor
.
BLUE
),
'blue'
)
self
.
assertEqual
(
ConfusedColor
.
RED
.
behavior
(),
'nuhuh!'
)
self
.
assertEqual
(
ConfusedColor
.
RED
.
social
(),
"what's up?"
)
class
ReformedColor
(
StrMixin
,
IntEnum
,
SomeEnum
,
AnotherEnum
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
ReformedColor
.
RED
.
value
,
1
)
self
.
assertEqual
(
ReformedColor
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
ReformedColor
.
BLUE
.
value
,
3
)
self
.
assertEqual
(
str
(
ReformedColor
.
BLUE
),
'blue'
)
self
.
assertEqual
(
ReformedColor
.
RED
.
behavior
(),
'booyah'
)
self
.
assertEqual
(
ConfusedColor
.
RED
.
social
(),
"what's up?"
)
self
.
assertTrue
(
issubclass
(
ReformedColor
,
int
))
class
TestOrder
(
unittest
.
TestCase
):
...
...
@@ -2093,6 +2205,49 @@ class TestFlag(unittest.TestCase):
d
=
6
self
.
assertEqual
(
repr
(
Bizarre
(
7
)),
'<Bizarre.d|c|b: 7>'
)
def
test_multiple_mixin
(
self
):
class
AllMixin
:
@
classproperty
def
ALL
(
cls
):
members
=
list
(
cls
)
all_value
=
None
if
members
:
all_value
=
members
[
0
]
for
member
in
members
[
1
:]:
all_value
|=
member
cls
.
ALL
=
all_value
return
all_value
class
StrMixin
:
def
__str__
(
self
):
return
self
.
_name_
.
lower
()
class
Color
(
AllMixin
,
Flag
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
4
)
self
.
assertEqual
(
Color
.
ALL
.
value
,
7
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'Color.BLUE'
)
class
Color
(
AllMixin
,
StrMixin
,
Flag
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
4
)
self
.
assertEqual
(
Color
.
ALL
.
value
,
7
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'blue'
)
class
Color
(
StrMixin
,
AllMixin
,
Flag
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
4
)
self
.
assertEqual
(
Color
.
ALL
.
value
,
7
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'blue'
)
@
support
.
reap_threads
def
test_unique_composite
(
self
):
# override __eq__ to be identity only
...
...
@@ -2468,6 +2623,49 @@ class TestIntFlag(unittest.TestCase):
for
f
in
Open
:
self
.
assertEqual
(
bool
(
f
.
value
),
bool
(
f
))
def
test_multiple_mixin
(
self
):
class
AllMixin
:
@
classproperty
def
ALL
(
cls
):
members
=
list
(
cls
)
all_value
=
None
if
members
:
all_value
=
members
[
0
]
for
member
in
members
[
1
:]:
all_value
|=
member
cls
.
ALL
=
all_value
return
all_value
class
StrMixin
:
def
__str__
(
self
):
return
self
.
_name_
.
lower
()
class
Color
(
AllMixin
,
IntFlag
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
4
)
self
.
assertEqual
(
Color
.
ALL
.
value
,
7
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'Color.BLUE'
)
class
Color
(
AllMixin
,
StrMixin
,
IntFlag
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
4
)
self
.
assertEqual
(
Color
.
ALL
.
value
,
7
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'blue'
)
class
Color
(
StrMixin
,
AllMixin
,
IntFlag
):
RED
=
auto
()
GREEN
=
auto
()
BLUE
=
auto
()
self
.
assertEqual
(
Color
.
RED
.
value
,
1
)
self
.
assertEqual
(
Color
.
GREEN
.
value
,
2
)
self
.
assertEqual
(
Color
.
BLUE
.
value
,
4
)
self
.
assertEqual
(
Color
.
ALL
.
value
,
7
)
self
.
assertEqual
(
str
(
Color
.
BLUE
),
'blue'
)
@
support
.
reap_threads
def
test_unique_composite
(
self
):
# override __eq__ to be identity only
...
...
@@ -2553,6 +2751,7 @@ class TestUnique(unittest.TestCase):
value
=
4
expected_help_output_with_docs
=
"""
\
Help on class Color in module %s:
...
...
Misc/NEWS.d/next/Library/2018-09-14-20-00-47.bpo-29577.RzwKFD.rst
0 → 100644
View file @
5bdab641
Support multiple mixin classes when creating Enums.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment