diff -r 41ce95a5b2d8 Doc/library/enum.rst --- a/Doc/library/enum.rst Sun Mar 08 09:17:28 2015 +0200 +++ b/Doc/library/enum.rst Sun Mar 08 15:33:06 2015 +0200 @@ -23,9 +23,9 @@ by identity, and the enumeration itself Module Contents --------------- -This module defines two enumeration classes that can be used to define unique -sets of names and values: :class:`Enum` and :class:`IntEnum`. It also defines -one decorator, :func:`unique`. +This module defines three enumeration classes that can be used to define unique +sets of names and values: :class:`Enum`, :class:`IntEnum` and :class:`IntFlags`. +It also defines one decorator, :func:`unique`. .. class:: Enum @@ -37,6 +37,11 @@ one decorator, :func:`unique`. Base class for creating enumerated constants that are also subclasses of :class:`int`. +.. class:: IntFlags + + Base class for creating enumerated constants that are also + subclasses of :class:`int` and are purposed to be used as bit flags. + .. function:: unique Enum class decorator that ensures only one name is bound to any one value. @@ -252,7 +257,7 @@ Enumeration members are compared by iden True Ordered comparisons between enumeration values are *not* supported. Enum -members are not integers (but see `IntEnum`_ below):: +members are not integers (but see `IntEnum`_ and `IntFlags`_ below):: >>> Color.red < Color.blue Traceback (most recent call last): @@ -269,8 +274,8 @@ Equality comparisons are defined though: True Comparisons against non-enumeration values will always compare not equal -(again, :class:`IntEnum` was explicitly designed to behave differently, see -below):: +(again, :class:`IntEnum` and :class:`IntFlags` were explicitly designed to +behave differently, see below):: >>> Color.blue == 2 False @@ -530,6 +535,33 @@ replaced with enumerations and backwards that still expects integers. +IntFlags +^^^^^^^^ + +A variation of :class:`IntEnum` is provided which is purposed to represent bit +flags. The result of bitwise operations (``|``, ``&``, ``^``, and ``~``) +preserves the type of :class:`IntFlags`. + + >>> from enum import IntFlags + >>> class Perm(IntFlags): + ... read = 1<<0 + ... write = 1<<1 + ... execute = 1<<2 + ... + >>> Perm.read | Perm.write + + >>> (Perm.read | Perm.write) & ~Perm.write + + +Functional interface of :class:`IntFlags` creates flags with values that are +powers of two:: + + >>> from enum import IntFlags + >>> Perm = IntFlags('Perm', 'read write execute') + >>> list(Perm) + [, , ] + + Others ^^^^^^ @@ -566,7 +598,8 @@ 5. :meth:`str.__format__` (or :func:`for Interesting examples -------------------- -While :class:`Enum` and :class:`IntEnum` are expected to cover the majority of +While :class:`Enum`, :class:`IntEnum` and :class:`IntFlags` are expected to +cover the majority of use-cases, they cannot cover them all. Here are recipes for some different types of enumerations that can be used directly, or as examples for creating one's own. diff -r 41ce95a5b2d8 Lib/enum.py --- a/Lib/enum.py Sun Mar 08 09:17:28 2015 +0200 +++ b/Lib/enum.py Sun Mar 08 15:33:06 2015 +0200 @@ -2,7 +2,7 @@ import sys from collections import OrderedDict from types import MappingProxyType, DynamicClassAttribute -__all__ = ['Enum', 'IntEnum', 'unique'] +__all__ = ['Enum', 'IntEnum', 'unique', 'IntFlags'] def _is_descriptor(obj): @@ -312,7 +312,7 @@ class EnumMeta(type): if isinstance(names, str): names = names.replace(',', ' ').split() if isinstance(names, (tuple, list)) and isinstance(names[0], str): - names = [(e, i) for (i, e) in enumerate(names, start)] + names = cls._gen_values_(names, start) # Here, names is either an iterable of (name, value) or a mapping. for item in names: @@ -339,6 +339,9 @@ class EnumMeta(type): return enum_class + def _gen_values_(cls, names, start): + return [(e, i) for (i, e) in enumerate(names, start)] + @staticmethod def _get_mixins_(bases): """Returns the type for creating enum members, and the first inherited @@ -527,3 +530,124 @@ def unique(enumeration): raise ValueError('duplicate values found in %r: %s' % (enumeration, alias_details)) return enumeration + + +class IntFlagsMeta(EnumMeta): + """Metaclass for IntFlags""" + + def __new__(metacls, cls, bases, classdict): + enum_class = EnumMeta.__new__(metacls, cls, bases, classdict) + enum_class.__new__ = enum_class.__new_member__ + return enum_class + + def _gen_values_(cls, names, start): + return [(e, start< 1 or value: + s = '~(%s)' % s + else: + s = '~%s' % s + return s + + def __repr__(self): + if self._name_ is not None: + return '<%s.%s: %r>' % ( + self.__class__.__name__, self._name_, self._value_) + invert, names, value = self._decompose() + if value or not names: + names.append(str(value)) + s = '|'.join('%s' % n for n in names) + if invert: + if len(names) > 1: + s = '~(%s)' % s + else: + s = '~%s' % s + return '<%s: %s = %r>' % (self.__class__.__name__, s, self._value_) + + def __or__(self, other): + if isinstance(other, self.__class__): + other = other._value_ + elif isinstance(other, IntFlags) or not isinstance(other, int): + return NotImplemented + return self.__class__(self._value_ | other) + + def __ror__(self, other): + if isinstance(other, self.__class__): + other = other._value_ + elif isinstance(other, IntFlags) or not isinstance(other, int): + return NotImplemented + return self.__class__(other | self._value_) + + def __and__(self, other): + if isinstance(other, self.__class__): + other = other._value_ + elif isinstance(other, IntFlags) or not isinstance(other, int): + return NotImplemented + return self.__class__(self._value_ & other) + + def __rand__(self, other): + if isinstance(other, self.__class__): + other = other._value_ + elif isinstance(other, IntFlags) or not isinstance(other, int): + return NotImplemented + return self.__class__(other & self._value_) + + def __xor__(self, other): + if isinstance(other, self.__class__): + other = other._value_ + elif isinstance(other, IntFlags) or not isinstance(other, int): + return NotImplemented + return self.__class__(self._value_ ^ other) + + def __rxor__(self, other): + if isinstance(other, self.__class__): + other = other._value_ + elif isinstance(other, IntFlags) or not isinstance(other, int): + return NotImplemented + return self.__class__(other ^ self._value_) + + def __invert__(self): + return self.__class__(~self._value_) diff -r 41ce95a5b2d8 Lib/os.py --- a/Lib/os.py Sun Mar 08 09:17:28 2015 +0200 +++ b/Lib/os.py Sun Mar 08 15:33:06 2015 +0200 @@ -23,6 +23,7 @@ and opendir), and leave all pathname man #' +import enum import sys, errno import stat as st @@ -115,6 +116,23 @@ from os.path import (curdir, pardir, sep del _names +OpenMode = enum.IntFlags('OpenMode', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith('O_')}) +globals().update(OpenMode.__members__) +StatFlags = enum.IntFlags('StatFlags', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith('ST_')}) +globals().update(StatFlags.__members__) +DLOpenFlags = enum.IntFlags('DLOpenFlags', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith('RTLD_')}) +globals().update(DLOpenFlags.__members__) +AccessMode = enum.IntFlags('AccessMode', + [(name, globals()[name]) for name in ('R_OK', 'W_OK', 'X_OK', 'F_OK') + if name in globals()]) +globals().update(AccessMode.__members__) + if _exists("_have_functions"): _globals = globals() diff -r 41ce95a5b2d8 Lib/re.py --- a/Lib/re.py Sun Mar 08 09:17:28 2015 +0200 +++ b/Lib/re.py Sun Mar 08 15:33:06 2015 +0200 @@ -119,6 +119,7 @@ This module also defines an exception 'e """ +import enum import sys import sre_compile import sre_parse @@ -139,17 +140,20 @@ except ImportError: __version__ = "2.2.1" # flags -A = ASCII = sre_compile.SRE_FLAG_ASCII # assume ascii "locale" -I = IGNORECASE = sre_compile.SRE_FLAG_IGNORECASE # ignore case -L = LOCALE = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale -U = UNICODE = sre_compile.SRE_FLAG_UNICODE # assume unicode "locale" -M = MULTILINE = sre_compile.SRE_FLAG_MULTILINE # make anchors look for newline -S = DOTALL = sre_compile.SRE_FLAG_DOTALL # make dot match newline -X = VERBOSE = sre_compile.SRE_FLAG_VERBOSE # ignore whitespace and comments +class Flags(enum.IntFlags): + A = ASCII = sre_compile.SRE_FLAG_ASCII # assume ascii "locale" + I = IGNORECASE = sre_compile.SRE_FLAG_IGNORECASE # ignore case + L = LOCALE = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale + U = UNICODE = sre_compile.SRE_FLAG_UNICODE # assume unicode "locale" + M = MULTILINE = sre_compile.SRE_FLAG_MULTILINE # make anchors look for newline + S = DOTALL = sre_compile.SRE_FLAG_DOTALL # make dot match newline + X = VERBOSE = sre_compile.SRE_FLAG_VERBOSE # ignore whitespace and comments -# sre extensions (experimental, don't rely on these) -T = TEMPLATE = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking -DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation + # sre extensions (experimental, don't rely on these) + T = TEMPLATE = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking + DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation + +globals().update(Flags.__members__) # sre exception error = sre_compile.error diff -r 41ce95a5b2d8 Lib/socket.py --- a/Lib/socket.py Sun Mar 08 09:17:28 2015 +0200 +++ b/Lib/socket.py Sun Mar 08 15:33:06 2015 +0200 @@ -50,7 +50,7 @@ import _socket from _socket import * import os, sys, io, selectors -from enum import IntEnum +from enum import IntEnum, IntFlags try: import errno @@ -79,6 +79,16 @@ SocketKind = IntEnum('SocketKind', if name.isupper() and name.startswith('SOCK_')}) globals().update(SocketKind.__members__) +MsgFlags = IntFlags('MsgFlags', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith('MSG_')}) +globals().update(MsgFlags.__members__) + +AddressInfo = IntFlags('AddressInfo', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith('AI_')}) +globals().update(AddressInfo.__members__) + _LOCALHOST = '127.0.0.1' _LOCALHOST_V6 = '::1' diff -r 41ce95a5b2d8 Lib/stat.py --- a/Lib/stat.py Sun Mar 08 09:17:28 2015 +0200 +++ b/Lib/stat.py Sun Mar 08 15:33:06 2015 +0200 @@ -2,6 +2,7 @@ Suggested usage: from stat import * """ +import enum # Indices for stat struct members in the tuple returned by os.stat() @@ -176,3 +177,18 @@ try: from _stat import * except ImportError: pass + +Permissions = enum.IntFlags('Permissions', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith('S_') and isinstance(value, int)}) +globals().update(Permissions.__members__) + +FileFlags = enum.IntFlags('FileFlags', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith(('UF_', 'SF_'))}) +globals().update(FileFlags.__members__) + +FileAttributes = enum.IntFlags('FileAttributes', + {name: value for name, value in globals().items() + if name.isupper() and name.startswith('FILE_ATTRIBUTE_')}) +globals().update(FileAttributes.__members__) diff -r 41ce95a5b2d8 Lib/test/test_enum.py --- a/Lib/test/test_enum.py Sun Mar 08 09:17:28 2015 +0200 +++ b/Lib/test/test_enum.py Sun Mar 08 15:33:06 2015 +0200 @@ -3,7 +3,7 @@ import inspect import pydoc import unittest from collections import OrderedDict -from enum import Enum, IntEnum, EnumMeta, unique +from enum import Enum, IntEnum, EnumMeta, unique, IntFlags from io import StringIO from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL @@ -1666,5 +1666,254 @@ class TestStdLib(unittest.TestCase): if failed: self.fail("result does not equal expected, see print above") + +class TestIntFlags(unittest.TestCase): + """Tests of the IntFlags.""" + + class Perm(IntFlags): + R = 1 << 0 + W = 1 << 1 + X = 1 << 2 + + class Open(IntFlags): + RO = 0 + WO = 1 + RW = 2 + AC = 3 + CE = 1<<19 + + def test_str(self): + Perm = self.Perm + self.assertEqual(str(Perm.R), 'Perm.R') + self.assertEqual(str(Perm.W), 'Perm.W') + self.assertEqual(str(Perm.X), 'Perm.X') + self.assertEqual(str(Perm.R | Perm.W), 'Perm.R|Perm.W') + self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|Perm.W|Perm.X') + self.assertEqual(str(Perm.R | 8), 'Perm.R|8') + self.assertEqual(str(Perm(0)), '0') + self.assertEqual(str(Perm(8)), '8') + self.assertEqual(str(~Perm.R), '~Perm.R') + self.assertEqual(str(~Perm.W), '~Perm.W') + self.assertEqual(str(~Perm.X), '~Perm.X') + self.assertEqual(str(~(Perm.R | Perm.W)), '~(Perm.R|Perm.W)') + self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), '~(Perm.R|Perm.W|Perm.X)') + self.assertEqual(str(~(Perm.R | 8)), '~(Perm.R|8)') + self.assertEqual(str(Perm(~0)), '~0') + self.assertEqual(str(Perm(~8)), '~8') + + Open = self.Open + self.assertEqual(str(Open.RO), 'Open.RO') + self.assertEqual(str(Open.WO), 'Open.WO') + self.assertEqual(str(Open.AC), 'Open.AC') + self.assertEqual(str(Open.RO | Open.CE), 'Open.CE') + self.assertEqual(str(Open.WO | Open.CE), 'Open.WO|Open.CE') + self.assertEqual(str(Open(4)), '4') + self.assertEqual(str(~Open.RO), '~Open.RO') + self.assertEqual(str(~Open.WO), '~Open.WO') + self.assertEqual(str(~Open.AC), '~Open.AC') + self.assertEqual(str(~(Open.RO | Open.CE)), '~Open.CE') + self.assertEqual(str(~(Open.WO | Open.CE)), '~(Open.WO|Open.CE)') + self.assertEqual(str(Open(~4)), '~4') + + def test_repr(self): + Perm = self.Perm + self.assertEqual(repr(Perm.R), '') + self.assertEqual(repr(Perm.W), '') + self.assertEqual(repr(Perm.X), '') + self.assertEqual(repr(Perm.R | Perm.W), '') + self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '') + self.assertEqual(repr(Perm.R | 8), '') + self.assertEqual(repr(Perm(0)), '') + self.assertEqual(repr(Perm(8)), '') + self.assertEqual(repr(~Perm.R), '') + self.assertEqual(repr(~Perm.W), '') + self.assertEqual(repr(~Perm.X), '') + self.assertEqual(repr(~(Perm.R | Perm.W)), '') + self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '') + self.assertEqual(repr(~(Perm.R | 8)), '') + self.assertEqual(repr(Perm(~0)), '') + self.assertEqual(repr(Perm(~8)), '') + + Open = self.Open + self.assertEqual(repr(Open.RO), '') + self.assertEqual(repr(Open.WO), '') + self.assertEqual(repr(Open.AC), '') + self.assertEqual(repr(Open.RO | Open.CE), '') + self.assertEqual(repr(Open.WO | Open.CE), '') + self.assertEqual(repr(Open(4)), '') + self.assertEqual(repr(~Open.RO), '') + self.assertEqual(repr(~Open.WO), '') + self.assertEqual(repr(~Open.AC), '') + self.assertEqual(repr(~(Open.RO | Open.CE)), '') + self.assertEqual(repr(~(Open.WO | Open.CE)), '') + self.assertEqual(repr(Open(~4)), '') + + def test_or(self): + Perm = self.Perm + for i in Perm: + for j in Perm: + self.assertEqual(i | j, i.value | j.value) + self.assertEqual((i | j).value, i.value | j.value) + self.assertIs(type(i | j), Perm) + for j in range(8): + self.assertEqual(i | j, i.value | j) + self.assertEqual((i | j).value, i.value | j) + self.assertIs(type(i | j), Perm) + self.assertEqual(j | i, j | i.value) + self.assertEqual((j | i).value, j | i.value) + self.assertIs(type(j | i), Perm) + for i in Perm: + self.assertIs(i | i, i) + self.assertIs(i | 0, i) + self.assertIs(0 | i, i) + Open = self.Open + self.assertIs(Open.RO | Open.CE, Open.CE) + + def test_and(self): + Perm = self.Perm + RW = Perm.R | Perm.W + RX = Perm.R | Perm.X + WX = Perm.W | Perm.X + RWX = Perm.R | Perm.W | Perm.X + values = list(Perm) + [RW, RX, WX, RWX, Perm(0)] + for i in values: + for j in values: + self.assertEqual(i & j, i.value & j.value) + self.assertEqual((i & j).value, i.value & j.value) + self.assertIs(type(i & j), Perm) + for j in range(8): + self.assertEqual(i & j, i.value & j) + self.assertEqual((i & j).value, i.value & j) + self.assertIs(type(i & j), Perm) + self.assertEqual(j & i, j & i.value) + self.assertEqual((j & i).value, j & i.value) + self.assertIs(type(j & i), Perm) + for i in Perm: + self.assertIs(i & i, i) + self.assertIs(i & 7, i) + self.assertIs(7 & i, i) + Open = self.Open + self.assertIs(Open.RO & Open.CE, Open.RO) + + def test_xor(self): + Perm = self.Perm + for i in Perm: + for j in Perm: + self.assertEqual(i ^ j, i.value ^ j.value) + self.assertEqual((i ^ j).value, i.value ^ j.value) + self.assertIs(type(i ^ j), Perm) + for j in range(8): + self.assertEqual(i ^ j, i.value ^ j) + self.assertEqual((i ^ j).value, i.value ^ j) + self.assertIs(type(i ^ j), Perm) + self.assertEqual(j ^ i, j ^ i.value) + self.assertEqual((j ^ i).value, j ^ i.value) + self.assertIs(type(j ^ i), Perm) + for i in Perm: + self.assertIs(i ^ 0, i) + self.assertIs(0 ^ i, i) + Open = self.Open + self.assertIs(Open.RO ^ Open.CE, Open.CE) + self.assertIs(Open.CE ^ Open.CE, Open.RO) + + def test_invert(self): + Perm = self.Perm + RW = Perm.R | Perm.W + RX = Perm.R | Perm.X + WX = Perm.W | Perm.X + RWX = Perm.R | Perm.W | Perm.X + values = list(Perm) + [RW, RX, WX, RWX, Perm(0)] + for i in values: + self.assertEqual(~i, ~i.value) + self.assertEqual((~i).value, ~i.value) + self.assertIs(type(~i), Perm) + self.assertEqual(~~i, i) + for i in Perm: + self.assertIs(~~i, i) + Open = self.Open + self.assertIs(Open.WO & ~Open.WO, Open.RO) + self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE) + + def test_programatic_function_string(self): + Perm = IntFlags('Perm', 'R W X') + lst = list(Perm) + self.assertEqual(len(lst), len(Perm)) + self.assertEqual(len(Perm), 3, Perm) + self.assertEqual(lst, [Perm.R, Perm.W, Perm.X]) + for i, n in enumerate('R W X'.split()): + v = 1<