diff -r 2079a517193b Lib/enum.py --- a/Lib/enum.py Thu Jul 18 17:05:39 2013 -0700 +++ b/Lib/enum.py Fri Jul 19 18:46:20 2013 -0700 @@ -1,12 +1,10 @@ -"""Python Enumerations""" - import sys from collections import OrderedDict from types import MappingProxyType __all__ = ['Enum', 'IntEnum', 'unique'] class _RouteClassAttributeToGetattr: """Route attribute access on a class to __getattr__. @@ -120,25 +118,25 @@ class EnumMeta(type): del classdict[name] # check for illegal enum names (any others?) invalid_names = set(members) & {'mro', } if invalid_names: raise ValueError('Invalid enum member name: {0}'.format( ','.join(invalid_names))) # create our new Enum type enum_class = super().__new__(metacls, cls, bases, classdict) - enum_class._member_names = [] # names in definition order - enum_class._member_map = OrderedDict() # name->value map + enum_class._member_names_ = [] # names in definition order + enum_class._member_map_ = OrderedDict() # name->value map # Reverse value->name map for hashable values. - enum_class._value2member_map = {} + enum_class._value2member_map_ = {} # check for a __getnewargs__, and if not present sabotage # pickling, since it won't work anyway if (member_type is not object and member_type.__dict__.get('__getnewargs__') is None ): _make_class_unpicklable(enum_class) # instantiate them, checking for duplicates as we go # we instantiate first instead of checking for duplicates first in case @@ -147,43 +145,45 @@ class EnumMeta(type): for member_name in classdict._member_names: value = members[member_name] if not isinstance(value, tuple): args = (value, ) else: args = value if member_type is tuple: # special case for tuple enums args = (args, ) # wrap it one more time if not use_args: enum_member = __new__(enum_class) - enum_member._value = value + original_value = value else: enum_member = __new__(enum_class, *args) - if not hasattr(enum_member, '_value'): - enum_member._value = member_type(*args) - enum_member._member_type = member_type - enum_member._name = member_name + original_value = member_type(*args) + if not hasattr(enum_member, '_value_'): + enum_member._value_ = original_value + value = enum_member._value_ + enum_member._member_type_ = member_type + enum_member._name_ = member_name enum_member.__init__(*args) # If another member with the same value was already defined, the # new member becomes an alias to the existing one. - for name, canonical_member in enum_class._member_map.items(): - if canonical_member.value == enum_member._value: + for name, canonical_member in enum_class._member_map_.items(): + if canonical_member.value == enum_member._value_: enum_member = canonical_member break else: # Aliases don't appear in member names (only in __members__). - enum_class._member_names.append(member_name) - enum_class._member_map[member_name] = enum_member + enum_class._member_names_.append(member_name) + enum_class._member_map_[member_name] = enum_member try: # This may fail if value is not hashable. We can't add the value # to the map, and by-value lookups for this value will be # linear. - enum_class._value2member_map[value] = enum_member + enum_class._value2member_map_[value] = enum_member except TypeError: pass # double check that repr and friends are not the mixin's or various # things break (such as pickle) for name in ('__repr__', '__str__', '__getnewargs__'): class_method = getattr(enum_class, name) obj_method = getattr(member_type, name, None) enum_method = getattr(first_enum, name, None) if obj_method is not None and obj_method is class_method: @@ -214,59 +214,59 @@ class EnumMeta(type): calling module by walking the frame stack; if this is unsuccessful the resulting class will not be pickleable. """ if names is None: # simple value lookup return cls.__new__(cls, value) # otherwise, functional API: we're creating a new Enum type return cls._create_(value, names, module=module, type=type) 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_ def __dir__(self): - return ['__class__', '__doc__', '__members__'] + self._member_names + return ['__class__', '__doc__', '__members__'] + self._member_names_ @property def __members__(cls): """Returns a mapping of member name->value. This mapping lists all enum members, including aliases. Note that this is a read-only view of the internal mapping. """ - return MappingProxyType(cls._member_map) + return MappingProxyType(cls._member_map_) def __getattr__(cls, name): """Return the enum member matching `name` We use __getattr__ instead of descriptors or inserting into the enum class' __dict__ in order to support `name` and `value` being both properties for enum members (which live in the class' __dict__) and enum members themselves. """ if _is_dunder(name): raise AttributeError(name) try: - return cls._member_map[name] + return cls._member_map_[name] except KeyError: raise AttributeError(name) from None def __getitem__(cls, name): - return cls._member_map[name] + return cls._member_map_[name] def __iter__(cls): - return (cls._member_map[name] for name in cls._member_names) + return (cls._member_map_[name] for name in cls._member_names_) def __len__(cls): - return len(cls._member_names) + return len(cls._member_names_) def __repr__(cls): return "" % cls.__name__ def _create_(cls, class_name, names=None, *, module=None, type=None): """Convenience method to create a new Enum class. `names` can be: * A string containing member names, separated either with spaces or @@ -320,21 +320,21 @@ 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): + 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 @@ -409,63 +409,65 @@ class Enum(metaclass=EnumMeta): """ def __new__(cls, value): # all enum instances are actually created during class construction # without calling this method; this method is called by the metaclass' # __call__ (i.e. Color(3) ), and by pickle if type(value) is cls: # For lookups like Color(Color.red) return value # by-value search for a matching enum member # see if it's in the reverse mapping (for hashable values) - if value in cls._value2member_map: - return cls._value2member_map[value] - # not there, now do long search -- O(n) behavior - for member in cls._member_map.values(): - if member.value == value: - return member + try: + if value in cls._value2member_map_: + return cls._value2member_map_[value] + except TypeError: + # not there, now do long search -- O(n) behavior + for member in cls._member_map_.values(): + if member.value == value: + return member raise ValueError("%s is not a valid %s" % (value, cls.__name__)) def __repr__(self): return "<%s.%s: %r>" % ( - self.__class__.__name__, self._name, self._value) + self.__class__.__name__, self._name_, self._value_) def __str__(self): - return "%s.%s" % (self.__class__.__name__, self._name) + return "%s.%s" % (self.__class__.__name__, self._name_) def __dir__(self): return (['__class__', '__doc__', 'name', 'value']) def __eq__(self, other): if type(other) is self.__class__: return self is other return NotImplemented def __getnewargs__(self): - return (self._value, ) + return (self._value_, ) def __hash__(self): - return hash(self._name) + return hash(self._name_) # _RouteClassAttributeToGetattr is used to provide access to the `name` # and `value` properties of enum members while keeping some measure of # protection from modification, while still allowing for an enumeration # to have members named `name` and `value`. This works because enumeration # members are not set directly on the enum class -- __getattr__ is # used to look them up. @_RouteClassAttributeToGetattr def name(self): - return self._name + return self._name_ @_RouteClassAttributeToGetattr def value(self): - return self._value + return self._value_ class IntEnum(int, Enum): """Enum where members are also (and must be) ints""" def unique(enumeration): """Class decorator for enumerations ensuring unique member values.""" duplicates = [] for name, member in enumeration.__members__.items(): diff -r 2079a517193b Lib/test/test_enum.py --- a/Lib/test/test_enum.py Thu Jul 18 17:05:39 2013 -0700 +++ b/Lib/test/test_enum.py Fri Jul 19 18:46:20 2013 -0700 @@ -509,21 +509,21 @@ class TestEnum(unittest.TestCase): self.assertEqual(whatever.this.really(), 'no, not that') def test_overwrite_enums(self): class Why(Enum): question = 1 answer = 2 propisition = 3 def question(self): print(42) self.assertIsNot(type(Why.question), Why) - self.assertNotIn(Why.question, Why._member_names) + self.assertNotIn(Why.question, Why._member_names_) self.assertNotIn(Why.question, Why) def test_wrong_inheritance_order(self): with self.assertRaises(TypeError): class Wrong(Enum, str): NotHere = 'error before this point' def test_intenum_transitivity(self): class number(IntEnum): one = 1 @@ -769,78 +769,79 @@ class TestEnum(unittest.TestCase): self.assertIs(loads(dumps(SomeTuple.first)), SomeTuple.first) def test_duplicate_values_give_unique_enum_items(self): class AutoNumber(Enum): first = () second = () third = () def __new__(cls): value = len(cls.__members__) + 1 obj = object.__new__(cls) - obj._value = value + obj._value_ = value return obj def __int__(self): - return int(self._value) + return int(self._value_) self.assertEqual( list(AutoNumber), [AutoNumber.first, AutoNumber.second, AutoNumber.third], ) self.assertEqual(int(AutoNumber.second), 2) + self.assertEqual(AutoNumber.third.value, 3) self.assertIs(AutoNumber(1), AutoNumber.first) def test_inherited_new_from_enhanced_enum(self): class AutoNumber(Enum): def __new__(cls): value = len(cls.__members__) + 1 obj = object.__new__(cls) - obj._value = value + obj._value_ = value return obj def __int__(self): - return int(self._value) + return int(self._value_) class Color(AutoNumber): red = () green = () blue = () self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) self.assertEqual(list(map(int, Color)), [1, 2, 3]) def test_inherited_new_from_mixed_enum(self): class AutoNumber(IntEnum): def __new__(cls): value = len(cls.__members__) + 1 obj = int.__new__(cls, value) - obj._value = value + obj._value_ = value return obj class Color(AutoNumber): red = () green = () blue = () self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) self.assertEqual(list(map(int, Color)), [1, 2, 3]) def test_ordered_mixin(self): class OrderedEnum(Enum): def __ge__(self, other): if self.__class__ is other.__class__: - return self._value >= other._value + return self._value_ >= other._value_ return NotImplemented def __gt__(self, other): if self.__class__ is other.__class__: - return self._value > other._value + return self._value_ > other._value_ return NotImplemented def __le__(self, other): if self.__class__ is other.__class__: - return self._value <= other._value + return self._value_ <= other._value_ return NotImplemented def __lt__(self, other): if self.__class__ is other.__class__: - return self._value < other._value + return self._value_ < other._value_ return NotImplemented class Grade(OrderedEnum): A = 5 B = 4 C = 3 D = 2 F = 1 self.assertGreater(Grade.A, Grade.B) self.assertLessEqual(Grade.F, Grade.C) self.assertLess(Grade.D, Grade.A) @@ -909,20 +910,36 @@ class TestEnum(unittest.TestCase): self.mass = mass # in kilograms self.radius = radius # in meters @property def surface_gravity(self): # universal gravitational constant (m3 kg-1 s-2) G = 6.67300E-11 return G * self.mass / (self.radius * self.radius) self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80) self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6)) + def test_nonhash_value(self): + class AutoNumberInAList(Enum): + def __new__(cls): + value = [len(cls.__members__) + 1] + obj = object.__new__(cls) + obj._value_ = value + return obj + class ColorInAList(AutoNumberInAList): + red = () + green = () + blue = () + self.assertEqual(list(ColorInAList), [ColorInAList.red, ColorInAList.green, ColorInAList.blue]) + self.assertEqual(ColorInAList.red.value, [1]) + self.assertEqual(ColorInAList([1]), ColorInAList.red) + + class TestUnique(unittest.TestCase): def test_unique_clean(self): @unique class Clean(Enum): one = 1 two = 'dos' tres = 4.0 @unique