Skip to content

Commit 3515dcc

Browse files
committedSep 18, 2016
issue23591: fix flag decomposition and repr
1 parent 9a7bbb2 commit 3515dcc

File tree

3 files changed

+193
-86
lines changed

3 files changed

+193
-86
lines changed
 

‎Doc/library/enum.rst

+22
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,8 @@ while combinations of flags won't::
674674
... green = auto()
675675
... white = red | blue | green
676676
...
677+
>>> Color.white
678+
<Color.white: 7>
677679

678680
Giving a name to the "no flags set" condition does not change its boolean
679681
value::
@@ -1068,3 +1070,23 @@ but not of the class::
10681070
>>> dir(Planet.EARTH)
10691071
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
10701072

1073+
1074+
Combining members of ``Flag``
1075+
"""""""""""""""""""""""""""""
1076+
1077+
If a combination of Flag members is not named, the :func:`repr` will include
1078+
all named flags and all named combinations of flags that are in the value::
1079+
1080+
>>> class Color(Flag):
1081+
... red = auto()
1082+
... green = auto()
1083+
... blue = auto()
1084+
... magenta = red | blue
1085+
... yellow = red | green
1086+
... cyan = green | blue
1087+
...
1088+
>>> Color(3) # named combination
1089+
<Color.yellow: 3>
1090+
>>> Color(7) # not named combination
1091+
<Color.cyan|magenta|blue|yellow|green|red: 7>
1092+

‎Lib/enum.py

+94-58
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sys
22
from types import MappingProxyType, DynamicClassAttribute
33
from functools import reduce
4-
from operator import or_ as _or_
4+
from operator import or_ as _or_, and_ as _and_, xor, neg
55

66
# try _collections first to reduce startup cost
77
try:
@@ -47,11 +47,12 @@ def _break_on_call_reduce(self, proto):
4747
cls.__reduce_ex__ = _break_on_call_reduce
4848
cls.__module__ = '<unknown>'
4949

50+
_auto_null = object()
5051
class auto:
5152
"""
5253
Instances are replaced with an appropriate value in Enum class suites.
5354
"""
54-
pass
55+
value = _auto_null
5556

5657

5758
class _EnumDict(dict):
@@ -77,7 +78,7 @@ def __setitem__(self, key, value):
7778
"""
7879
if _is_sunder(key):
7980
if key not in (
80-
'_order_', '_create_pseudo_member_', '_decompose_',
81+
'_order_', '_create_pseudo_member_',
8182
'_generate_next_value_', '_missing_',
8283
):
8384
raise ValueError('_names_ are reserved for future Enum use')
@@ -94,7 +95,9 @@ def __setitem__(self, key, value):
9495
# enum overwriting a descriptor?
9596
raise TypeError('%r already defined as: %r' % (key, self[key]))
9697
if isinstance(value, auto):
97-
value = self._generate_next_value(key, 1, len(self._member_names), self._last_values[:])
98+
if value.value == _auto_null:
99+
value.value = self._generate_next_value(key, 1, len(self._member_names), self._last_values[:])
100+
value = value.value
98101
self._member_names.append(key)
99102
self._last_values.append(value)
100103
super().__setitem__(key, value)
@@ -658,7 +661,7 @@ def _generate_next_value_(name, start, count, last_values):
658661
try:
659662
high_bit = _high_bit(last_value)
660663
break
661-
except TypeError:
664+
except Exception:
662665
raise TypeError('Invalid Flag value: %r' % last_value) from None
663666
return 2 ** (high_bit+1)
664667

@@ -668,61 +671,38 @@ def _missing_(cls, value):
668671
if value < 0:
669672
value = ~value
670673
possible_member = cls._create_pseudo_member_(value)
671-
for member in possible_member._decompose_():
672-
if member._name_ is None and member._value_ != 0:
673-
raise ValueError('%r is not a valid %s' % (original_value, cls.__name__))
674674
if original_value < 0:
675675
possible_member = ~possible_member
676676
return possible_member
677677

678678
@classmethod
679679
def _create_pseudo_member_(cls, value):
680+
"""
681+
Create a composite member iff value contains only members.
682+
"""
680683
pseudo_member = cls._value2member_map_.get(value, None)
681684
if pseudo_member is None:
682-
# construct a non-singleton enum pseudo-member
685+
# verify all bits are accounted for
686+
_, extra_flags = _decompose(cls, value)
687+
if extra_flags:
688+
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
689+
# construct a singleton enum pseudo-member
683690
pseudo_member = object.__new__(cls)
684691
pseudo_member._name_ = None
685692
pseudo_member._value_ = value
686693
cls._value2member_map_[value] = pseudo_member
687694
return pseudo_member
688695

689-
def _decompose_(self):
690-
"""Extract all members from the value."""
691-
value = self._value_
692-
members = []
693-
cls = self.__class__
694-
for member in sorted(cls, key=lambda m: m._value_, reverse=True):
695-
while _high_bit(value) > _high_bit(member._value_):
696-
unknown = self._create_pseudo_member_(2 ** _high_bit(value))
697-
members.append(unknown)
698-
value &= ~unknown._value_
699-
if (
700-
(value & member._value_ == member._value_)
701-
and (member._value_ or not members)
702-
):
703-
value &= ~member._value_
704-
members.append(member)
705-
if not members or value:
706-
members.append(self._create_pseudo_member_(value))
707-
members = list(members)
708-
return members
709-
710696
def __contains__(self, other):
711697
if not isinstance(other, self.__class__):
712698
return NotImplemented
713699
return other._value_ & self._value_ == other._value_
714700

715-
def __iter__(self):
716-
if self.value == 0:
717-
return iter([])
718-
else:
719-
return iter(self._decompose_())
720-
721701
def __repr__(self):
722702
cls = self.__class__
723703
if self._name_ is not None:
724704
return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_)
725-
members = self._decompose_()
705+
members, uncovered = _decompose(cls, self._value_)
726706
return '<%s.%s: %r>' % (
727707
cls.__name__,
728708
'|'.join([str(m._name_ or m._value_) for m in members]),
@@ -733,7 +713,7 @@ def __str__(self):
733713
cls = self.__class__
734714
if self._name_ is not None:
735715
return '%s.%s' % (cls.__name__, self._name_)
736-
members = self._decompose_()
716+
members, uncovered = _decompose(cls, self._value_)
737717
if len(members) == 1 and members[0]._name_ is None:
738718
return '%s.%r' % (cls.__name__, members[0]._value_)
739719
else:
@@ -761,35 +741,58 @@ def __xor__(self, other):
761741
return self.__class__(self._value_ ^ other._value_)
762742

763743
def __invert__(self):
764-
members = self._decompose_()
765-
inverted_members = [m for m in self.__class__ if m not in members and not m._value_ & self._value_]
744+
members, uncovered = _decompose(self.__class__, self._value_)
745+
inverted_members = [
746+
m for m in self.__class__
747+
if m not in members and not m._value_ & self._value_
748+
]
766749
inverted = reduce(_or_, inverted_members, self.__class__(0))
767750
return self.__class__(inverted)
768751

769752

770753
class IntFlag(int, Flag):
771754
"""Support for integer-based Flags"""
772755

756+
@classmethod
757+
def _missing_(cls, value):
758+
if not isinstance(value, int):
759+
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
760+
new_member = cls._create_pseudo_member_(value)
761+
return new_member
762+
773763
@classmethod
774764
def _create_pseudo_member_(cls, value):
775765
pseudo_member = cls._value2member_map_.get(value, None)
776766
if pseudo_member is None:
777-
# construct a non-singleton enum pseudo-member
778-
pseudo_member = int.__new__(cls, value)
779-
pseudo_member._name_ = None
780-
pseudo_member._value_ = value
781-
cls._value2member_map_[value] = pseudo_member
767+
need_to_create = [value]
768+
# get unaccounted for bits
769+
_, extra_flags = _decompose(cls, value)
770+
# timer = 10
771+
while extra_flags:
772+
# timer -= 1
773+
bit = _high_bit(extra_flags)
774+
flag_value = 2 ** bit
775+
if (flag_value not in cls._value2member_map_ and
776+
flag_value not in need_to_create
777+
):
778+
need_to_create.append(flag_value)
779+
if extra_flags == -flag_value:
780+
extra_flags = 0
781+
else:
782+
extra_flags ^= flag_value
783+
for value in reversed(need_to_create):
784+
# construct singleton pseudo-members
785+
pseudo_member = int.__new__(cls, value)
786+
pseudo_member._name_ = None
787+
pseudo_member._value_ = value
788+
cls._value2member_map_[value] = pseudo_member
782789
return pseudo_member
783790

784-
@classmethod
785-
def _missing_(cls, value):
786-
possible_member = cls._create_pseudo_member_(value)
787-
return possible_member
788-
789791
def __or__(self, other):
790792
if not isinstance(other, (self.__class__, int)):
791793
return NotImplemented
792-
return self.__class__(self._value_ | self.__class__(other)._value_)
794+
result = self.__class__(self._value_ | self.__class__(other)._value_)
795+
return result
793796

794797
def __and__(self, other):
795798
if not isinstance(other, (self.__class__, int)):
@@ -806,17 +809,13 @@ def __xor__(self, other):
806809
__rxor__ = __xor__
807810

808811
def __invert__(self):
809-
# members = self._decompose_()
810-
# inverted_members = [m for m in self.__class__ if m not in members and not m._value_ & self._value_]
811-
# inverted = reduce(_or_, inverted_members, self.__class__(0))
812-
return self.__class__(~self._value_)
813-
814-
812+
result = self.__class__(~self._value_)
813+
return result
815814

816815

817816
def _high_bit(value):
818817
"""returns index of highest bit, or -1 if value is zero or negative"""
819-
return value.bit_length() - 1 if value > 0 else -1
818+
return value.bit_length() - 1
820819

821820
def unique(enumeration):
822821
"""Class decorator for enumerations ensuring unique member values."""
@@ -830,3 +829,40 @@ def unique(enumeration):
830829
raise ValueError('duplicate values found in %r: %s' %
831830
(enumeration, alias_details))
832831
return enumeration
832+
833+
def _decompose(flag, value):
834+
"""Extract all members from the value."""
835+
# _decompose is only called if the value is not named
836+
not_covered = value
837+
negative = value < 0
838+
if negative:
839+
# only check for named flags
840+
flags_to_check = [
841+
(m, v)
842+
for v, m in flag._value2member_map_.items()
843+
if m.name is not None
844+
]
845+
else:
846+
# check for named flags and powers-of-two flags
847+
flags_to_check = [
848+
(m, v)
849+
for v, m in flag._value2member_map_.items()
850+
if m.name is not None or _power_of_two(v)
851+
]
852+
members = []
853+
for member, member_value in flags_to_check:
854+
if member_value and member_value & value == member_value:
855+
members.append(member)
856+
not_covered &= ~member_value
857+
if not members and value in flag._value2member_map_:
858+
members.append(flag._value2member_map_[value])
859+
members.sort(key=lambda m: m._value_, reverse=True)
860+
if len(members) > 1 and members[0].value == value:
861+
# we have the breakdown, don't need the value member itself
862+
members.pop(0)
863+
return members, not_covered
864+
865+
def _power_of_two(value):
866+
if value < 1:
867+
return False
868+
return value == 2 ** _high_bit(value)

0 commit comments

Comments
 (0)
Please sign in to comment.