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
2c669184
Commit
2c669184
authored
Feb 08, 2014
by
Charles-François Natali
Browse files
Options
Browse Files
Download
Plain Diff
Merge.
parents
4025ac75
ca1b794d
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
233 additions
and
21 deletions
+233
-21
Doc/library/enum.rst
Doc/library/enum.rst
+11
-3
Lib/enum.py
Lib/enum.py
+17
-11
Lib/test/test_enum.py
Lib/test/test_enum.py
+205
-7
No files found.
Doc/library/enum.rst
View file @
2c669184
...
@@ -369,10 +369,10 @@ The usual restrictions for pickling apply: picklable enums must be defined in
...
@@ -369,10 +369,10 @@ The usual restrictions for pickling apply: picklable enums must be defined in
the top level of a module, since unpickling requires them to be importable
the top level of a module, since unpickling requires them to be importable
from that module.
from that module.
..
warning
::
..
note
::
In order to support the singleton nature of enumeration members, pickle
With pickle protocol version 4 it is possible to easily pickle enums
protocol version 2 or higher must be used
.
nested in other classes
.
Functional API
Functional API
...
@@ -420,6 +420,14 @@ The solution is to specify the module name explicitly as follows::
...
@@ -420,6 +420,14 @@ The solution is to specify the module name explicitly as follows::
>>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__)
>>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__)
The new pickle protocol 4 also, in some circumstances, relies on
:attr:``__qualname__`` being set to the location where pickle will be able
to find the class. For example, if the class was made available in class
SomeData in the global scope::
>>> Animals = Enum('Animals', 'ant bee cat dog', qualname='SomeData.Animals')
Derived Enumerations
Derived Enumerations
--------------------
--------------------
...
...
Lib/enum.py
View file @
2c669184
...
@@ -31,9 +31,9 @@ def _is_sunder(name):
...
@@ -31,9 +31,9 @@ def _is_sunder(name):
def
_make_class_unpicklable
(
cls
):
def
_make_class_unpicklable
(
cls
):
"""Make the given class un-picklable."""
"""Make the given class un-picklable."""
def
_break_on_call_reduce
(
self
):
def
_break_on_call_reduce
(
self
,
proto
):
raise
TypeError
(
'%r cannot be pickled'
%
self
)
raise
TypeError
(
'%r cannot be pickled'
%
self
)
cls
.
__reduce__
=
_break_on_call_reduce
cls
.
__reduce_
ex_
_
=
_break_on_call_reduce
cls
.
__module__
=
'<unknown>'
cls
.
__module__
=
'<unknown>'
...
@@ -115,11 +115,12 @@ class EnumMeta(type):
...
@@ -115,11 +115,12 @@ class EnumMeta(type):
# Reverse value->name map for hashable values.
# Reverse value->name map for hashable values.
enum_class
.
_value2member_map_
=
{}
enum_class
.
_value2member_map_
=
{}
# check for a
__getnewargs__
, and if not present sabotage
# check for a
supported pickle protocols
, and if not present sabotage
# pickling, since it won't work anyway
# pickling, since it won't work anyway
if
(
member_type
is
not
object
and
if
member_type
is
not
object
:
member_type
.
__dict__
.
get
(
'__getnewargs__'
)
is
None
methods
=
(
'__getnewargs_ex__'
,
'__getnewargs__'
,
):
'__reduce_ex__'
,
'__reduce__'
)
if
not
any
(
map
(
member_type
.
__dict__
.
get
,
methods
)):
_make_class_unpicklable
(
enum_class
)
_make_class_unpicklable
(
enum_class
)
# instantiate them, checking for duplicates as we go
# instantiate them, checking for duplicates as we go
...
@@ -166,7 +167,7 @@ class EnumMeta(type):
...
@@ -166,7 +167,7 @@ class EnumMeta(type):
# double check that repr and friends are not the mixin's or various
# double check that repr and friends are not the mixin's or various
# things break (such as pickle)
# things break (such as pickle)
for
name
in
(
'__repr__'
,
'__str__'
,
'__format__'
,
'__getnewargs__'
):
for
name
in
(
'__repr__'
,
'__str__'
,
'__format__'
,
'__getnewargs__'
,
'__reduce_ex__'
):
class_method
=
getattr
(
enum_class
,
name
)
class_method
=
getattr
(
enum_class
,
name
)
obj_method
=
getattr
(
member_type
,
name
,
None
)
obj_method
=
getattr
(
member_type
,
name
,
None
)
enum_method
=
getattr
(
first_enum
,
name
,
None
)
enum_method
=
getattr
(
first_enum
,
name
,
None
)
...
@@ -183,7 +184,7 @@ class EnumMeta(type):
...
@@ -183,7 +184,7 @@ class EnumMeta(type):
enum_class
.
__new__
=
Enum
.
__new__
enum_class
.
__new__
=
Enum
.
__new__
return
enum_class
return
enum_class
def
__call__
(
cls
,
value
,
names
=
None
,
*
,
module
=
None
,
type
=
None
):
def
__call__
(
cls
,
value
,
names
=
None
,
*
,
module
=
None
,
qualname
=
None
,
type
=
None
):
"""Either returns an existing member, or creates a new enum class.
"""Either returns an existing member, or creates a new enum class.
This method is used both when an enum class is given a value to match
This method is used both when an enum class is given a value to match
...
@@ -202,7 +203,7 @@ class EnumMeta(type):
...
@@ -202,7 +203,7 @@ class EnumMeta(type):
if
names
is
None
:
# simple value lookup
if
names
is
None
:
# simple value lookup
return
cls
.
__new__
(
cls
,
value
)
return
cls
.
__new__
(
cls
,
value
)
# otherwise, functional API: we're creating a new Enum type
# otherwise, functional API: we're creating a new Enum type
return
cls
.
_create_
(
value
,
names
,
module
=
module
,
type
=
type
)
return
cls
.
_create_
(
value
,
names
,
module
=
module
,
qualname
=
qualname
,
type
=
type
)
def
__contains__
(
cls
,
member
):
def
__contains__
(
cls
,
member
):
return
isinstance
(
member
,
cls
)
and
member
.
name
in
cls
.
_member_map_
return
isinstance
(
member
,
cls
)
and
member
.
name
in
cls
.
_member_map_
...
@@ -273,7 +274,7 @@ class EnumMeta(type):
...
@@ -273,7 +274,7 @@ class EnumMeta(type):
raise
AttributeError
(
'Cannot reassign members.'
)
raise
AttributeError
(
'Cannot reassign members.'
)
super
().
__setattr__
(
name
,
value
)
super
().
__setattr__
(
name
,
value
)
def
_create_
(
cls
,
class_name
,
names
=
None
,
*
,
module
=
None
,
type
=
None
):
def
_create_
(
cls
,
class_name
,
names
=
None
,
*
,
module
=
None
,
qualname
=
None
,
type
=
None
):
"""Convenience method to create a new Enum class.
"""Convenience method to create a new Enum class.
`names` can be:
`names` can be:
...
@@ -315,6 +316,8 @@ class EnumMeta(type):
...
@@ -315,6 +316,8 @@ class EnumMeta(type):
_make_class_unpicklable
(
enum_class
)
_make_class_unpicklable
(
enum_class
)
else
:
else
:
enum_class
.
__module__
=
module
enum_class
.
__module__
=
module
if
qualname
is
not
None
:
enum_class
.
__qualname__
=
qualname
return
enum_class
return
enum_class
...
@@ -468,6 +471,9 @@ class Enum(metaclass=EnumMeta):
...
@@ -468,6 +471,9 @@ class Enum(metaclass=EnumMeta):
def
__hash__
(
self
):
def
__hash__
(
self
):
return
hash
(
self
.
_name_
)
return
hash
(
self
.
_name_
)
def
__reduce_ex__
(
self
,
proto
):
return
self
.
__class__
,
self
.
__getnewargs__
()
# DynamicClassAttribute is used to provide access to the `name` and
# DynamicClassAttribute is used to provide access to the `name` and
# `value` properties of enum members while keeping some measure of
# `value` properties of enum members while keeping some measure of
# protection from modification, while still allowing for an enumeration
# protection from modification, while still allowing for an enumeration
...
...
Lib/test/test_enum.py
View file @
2c669184
...
@@ -52,6 +52,11 @@ try:
...
@@ -52,6 +52,11 @@ try:
except
Exception
as
exc
:
except
Exception
as
exc
:
Answer
=
exc
Answer
=
exc
try
:
Theory
=
Enum
(
'Theory'
,
'rule law supposition'
,
qualname
=
'spanish_inquisition'
)
except
Exception
as
exc
:
Theory
=
exc
# for doctests
# for doctests
try
:
try
:
class
Fruit
(
Enum
):
class
Fruit
(
Enum
):
...
@@ -61,14 +66,18 @@ try:
...
@@ -61,14 +66,18 @@ try:
except
Exception
:
except
Exception
:
pass
pass
def
test_pickle_dump_load
(
assertion
,
source
,
target
=
None
):
def
test_pickle_dump_load
(
assertion
,
source
,
target
=
None
,
*
,
protocol
=
(
0
,
HIGHEST_PROTOCOL
)):
start
,
stop
=
protocol
if
target
is
None
:
if
target
is
None
:
target
=
source
target
=
source
for
protocol
in
range
(
2
,
HIGHEST_PROTOCOL
+
1
):
for
protocol
in
range
(
start
,
stop
+
1
):
assertion
(
loads
(
dumps
(
source
,
protocol
=
protocol
)),
target
)
assertion
(
loads
(
dumps
(
source
,
protocol
=
protocol
)),
target
)
def
test_pickle_exception
(
assertion
,
exception
,
obj
):
def
test_pickle_exception
(
assertion
,
exception
,
obj
,
for
protocol
in
range
(
2
,
HIGHEST_PROTOCOL
+
1
):
*
,
protocol
=
(
0
,
HIGHEST_PROTOCOL
)):
start
,
stop
=
protocol
for
protocol
in
range
(
start
,
stop
+
1
):
with
assertion
(
exception
):
with
assertion
(
exception
):
dumps
(
obj
,
protocol
=
protocol
)
dumps
(
obj
,
protocol
=
protocol
)
...
@@ -101,6 +110,7 @@ class TestHelpers(unittest.TestCase):
...
@@ -101,6 +110,7 @@ class TestHelpers(unittest.TestCase):
class
TestEnum
(
unittest
.
TestCase
):
class
TestEnum
(
unittest
.
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
class
Season
(
Enum
):
class
Season
(
Enum
):
SPRING
=
1
SPRING
=
1
...
@@ -540,11 +550,31 @@ class TestEnum(unittest.TestCase):
...
@@ -540,11 +550,31 @@ class TestEnum(unittest.TestCase):
test_pickle_dump_load
(
self
.
assertIs
,
Question
.
who
)
test_pickle_dump_load
(
self
.
assertIs
,
Question
.
who
)
test_pickle_dump_load
(
self
.
assertIs
,
Question
)
test_pickle_dump_load
(
self
.
assertIs
,
Question
)
def
test_enum_function_with_qualname
(
self
):
if
isinstance
(
Theory
,
Exception
):
raise
Theory
self
.
assertEqual
(
Theory
.
__qualname__
,
'spanish_inquisition'
)
def
test_class_nested_enum_and_pickle_protocol_four
(
self
):
# would normally just have this directly in the class namespace
class
NestedEnum
(
Enum
):
twigs
=
'common'
shiny
=
'rare'
self
.
__class__
.
NestedEnum
=
NestedEnum
self
.
NestedEnum
.
__qualname__
=
'%s.NestedEnum'
%
self
.
__class__
.
__name__
test_pickle_exception
(
self
.
assertRaises
,
PicklingError
,
self
.
NestedEnum
.
twigs
,
protocol
=
(
0
,
3
))
test_pickle_dump_load
(
self
.
assertIs
,
self
.
NestedEnum
.
twigs
,
protocol
=
(
4
,
HIGHEST_PROTOCOL
))
def
test_exploding_pickle
(
self
):
def
test_exploding_pickle
(
self
):
BadPickle
=
Enum
(
'BadPickle'
,
'dill sweet bread-n-butter'
)
BadPickle
=
Enum
(
BadPickle
.
__qualname__
=
'BadPickle'
# needed for pickle protocol 4
'BadPickle'
,
'dill sweet bread-n-butter'
,
module
=
__name__
)
globals
()[
'BadPickle'
]
=
BadPickle
globals
()[
'BadPickle'
]
=
BadPickle
enum
.
_make_class_unpicklable
(
BadPickle
)
# will overwrite __qualname__
# now break BadPickle to test exception raising
enum
.
_make_class_unpicklable
(
BadPickle
)
test_pickle_exception
(
self
.
assertRaises
,
TypeError
,
BadPickle
.
dill
)
test_pickle_exception
(
self
.
assertRaises
,
TypeError
,
BadPickle
.
dill
)
test_pickle_exception
(
self
.
assertRaises
,
PicklingError
,
BadPickle
)
test_pickle_exception
(
self
.
assertRaises
,
PicklingError
,
BadPickle
)
...
@@ -927,6 +957,174 @@ class TestEnum(unittest.TestCase):
...
@@ -927,6 +957,174 @@ class TestEnum(unittest.TestCase):
self
.
assertEqual
(
NEI
.
y
.
value
,
2
)
self
.
assertEqual
(
NEI
.
y
.
value
,
2
)
test_pickle_dump_load
(
self
.
assertIs
,
NEI
.
y
)
test_pickle_dump_load
(
self
.
assertIs
,
NEI
.
y
)
def
test_subclasses_with_getnewargs_ex
(
self
):
class
NamedInt
(
int
):
__qualname__
=
'NamedInt'
# needed for pickle protocol 4
def
__new__
(
cls
,
*
args
):
_args
=
args
name
,
*
args
=
args
if
len
(
args
)
==
0
:
raise
TypeError
(
"name and value must be specified"
)
self
=
int
.
__new__
(
cls
,
*
args
)
self
.
_intname
=
name
self
.
_args
=
_args
return
self
def
__getnewargs_ex__
(
self
):
return
self
.
_args
,
{}
@
property
def
__name__
(
self
):
return
self
.
_intname
def
__repr__
(
self
):
# repr() is updated to include the name and type info
return
"{}({!r}, {})"
.
format
(
type
(
self
).
__name__
,
self
.
__name__
,
int
.
__repr__
(
self
))
def
__str__
(
self
):
# str() is unchanged, even if it relies on the repr() fallback
base
=
int
base_str
=
base
.
__str__
if
base_str
.
__objclass__
is
object
:
return
base
.
__repr__
(
self
)
return
base_str
(
self
)
# for simplicity, we only define one operator that
# propagates expressions
def
__add__
(
self
,
other
):
temp
=
int
(
self
)
+
int
(
other
)
if
isinstance
(
self
,
NamedInt
)
and
isinstance
(
other
,
NamedInt
):
return
NamedInt
(
'({0} + {1})'
.
format
(
self
.
__name__
,
other
.
__name__
),
temp
)
else
:
return
temp
class
NEI
(
NamedInt
,
Enum
):
__qualname__
=
'NEI'
# needed for pickle protocol 4
x
=
(
'the-x'
,
1
)
y
=
(
'the-y'
,
2
)
self
.
assertIs
(
NEI
.
__new__
,
Enum
.
__new__
)
self
.
assertEqual
(
repr
(
NEI
.
x
+
NEI
.
y
),
"NamedInt('(the-x + the-y)', 3)"
)
globals
()[
'NamedInt'
]
=
NamedInt
globals
()[
'NEI'
]
=
NEI
NI5
=
NamedInt
(
'test'
,
5
)
self
.
assertEqual
(
NI5
,
5
)
test_pickle_dump_load
(
self
.
assertEqual
,
NI5
,
5
,
protocol
=
(
4
,
4
))
self
.
assertEqual
(
NEI
.
y
.
value
,
2
)
test_pickle_dump_load
(
self
.
assertIs
,
NEI
.
y
,
protocol
=
(
4
,
4
))
def
test_subclasses_with_reduce
(
self
):
class
NamedInt
(
int
):
__qualname__
=
'NamedInt'
# needed for pickle protocol 4
def
__new__
(
cls
,
*
args
):
_args
=
args
name
,
*
args
=
args
if
len
(
args
)
==
0
:
raise
TypeError
(
"name and value must be specified"
)
self
=
int
.
__new__
(
cls
,
*
args
)
self
.
_intname
=
name
self
.
_args
=
_args
return
self
def
__reduce__
(
self
):
return
self
.
__class__
,
self
.
_args
@
property
def
__name__
(
self
):
return
self
.
_intname
def
__repr__
(
self
):
# repr() is updated to include the name and type info
return
"{}({!r}, {})"
.
format
(
type
(
self
).
__name__
,
self
.
__name__
,
int
.
__repr__
(
self
))
def
__str__
(
self
):
# str() is unchanged, even if it relies on the repr() fallback
base
=
int
base_str
=
base
.
__str__
if
base_str
.
__objclass__
is
object
:
return
base
.
__repr__
(
self
)
return
base_str
(
self
)
# for simplicity, we only define one operator that
# propagates expressions
def
__add__
(
self
,
other
):
temp
=
int
(
self
)
+
int
(
other
)
if
isinstance
(
self
,
NamedInt
)
and
isinstance
(
other
,
NamedInt
):
return
NamedInt
(
'({0} + {1})'
.
format
(
self
.
__name__
,
other
.
__name__
),
temp
)
else
:
return
temp
class
NEI
(
NamedInt
,
Enum
):
__qualname__
=
'NEI'
# needed for pickle protocol 4
x
=
(
'the-x'
,
1
)
y
=
(
'the-y'
,
2
)
self
.
assertIs
(
NEI
.
__new__
,
Enum
.
__new__
)
self
.
assertEqual
(
repr
(
NEI
.
x
+
NEI
.
y
),
"NamedInt('(the-x + the-y)', 3)"
)
globals
()[
'NamedInt'
]
=
NamedInt
globals
()[
'NEI'
]
=
NEI
NI5
=
NamedInt
(
'test'
,
5
)
self
.
assertEqual
(
NI5
,
5
)
test_pickle_dump_load
(
self
.
assertEqual
,
NI5
,
5
)
self
.
assertEqual
(
NEI
.
y
.
value
,
2
)
test_pickle_dump_load
(
self
.
assertIs
,
NEI
.
y
)
def
test_subclasses_with_reduce_ex
(
self
):
class
NamedInt
(
int
):
__qualname__
=
'NamedInt'
# needed for pickle protocol 4
def
__new__
(
cls
,
*
args
):
_args
=
args
name
,
*
args
=
args
if
len
(
args
)
==
0
:
raise
TypeError
(
"name and value must be specified"
)
self
=
int
.
__new__
(
cls
,
*
args
)
self
.
_intname
=
name
self
.
_args
=
_args
return
self
def
__reduce_ex__
(
self
,
proto
):
return
self
.
__class__
,
self
.
_args
@
property
def
__name__
(
self
):
return
self
.
_intname
def
__repr__
(
self
):
# repr() is updated to include the name and type info
return
"{}({!r}, {})"
.
format
(
type
(
self
).
__name__
,
self
.
__name__
,
int
.
__repr__
(
self
))
def
__str__
(
self
):
# str() is unchanged, even if it relies on the repr() fallback
base
=
int
base_str
=
base
.
__str__
if
base_str
.
__objclass__
is
object
:
return
base
.
__repr__
(
self
)
return
base_str
(
self
)
# for simplicity, we only define one operator that
# propagates expressions
def
__add__
(
self
,
other
):
temp
=
int
(
self
)
+
int
(
other
)
if
isinstance
(
self
,
NamedInt
)
and
isinstance
(
other
,
NamedInt
):
return
NamedInt
(
'({0} + {1})'
.
format
(
self
.
__name__
,
other
.
__name__
),
temp
)
else
:
return
temp
class
NEI
(
NamedInt
,
Enum
):
__qualname__
=
'NEI'
# needed for pickle protocol 4
x
=
(
'the-x'
,
1
)
y
=
(
'the-y'
,
2
)
self
.
assertIs
(
NEI
.
__new__
,
Enum
.
__new__
)
self
.
assertEqual
(
repr
(
NEI
.
x
+
NEI
.
y
),
"NamedInt('(the-x + the-y)', 3)"
)
globals
()[
'NamedInt'
]
=
NamedInt
globals
()[
'NEI'
]
=
NEI
NI5
=
NamedInt
(
'test'
,
5
)
self
.
assertEqual
(
NI5
,
5
)
test_pickle_dump_load
(
self
.
assertEqual
,
NI5
,
5
)
self
.
assertEqual
(
NEI
.
y
.
value
,
2
)
test_pickle_dump_load
(
self
.
assertIs
,
NEI
.
y
)
def
test_subclasses_without_getnewargs
(
self
):
def
test_subclasses_without_getnewargs
(
self
):
class
NamedInt
(
int
):
class
NamedInt
(
int
):
__qualname__
=
'NamedInt'
__qualname__
=
'NamedInt'
...
...
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