diff options
author | Guido van Rossum <guido@python.org> | 2007-11-22 00:55:51 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2007-11-22 00:55:51 (GMT) |
commit | 64c06e327d48150fc548cf18a4a7ae0b890e69fa (patch) | |
tree | daf53ab57c369a3d92c5a9deafc41df2ccd96127 /Lib | |
parent | cc7f26bf207ee17e2c1b3e6545e145942aff612d (diff) | |
download | cpython-64c06e327d48150fc548cf18a4a7ae0b890e69fa.zip cpython-64c06e327d48150fc548cf18a4a7ae0b890e69fa.tar.gz cpython-64c06e327d48150fc548cf18a4a7ae0b890e69fa.tar.bz2 |
Backport of _abccoll.py by Benjamin Arangueren, issue 1383.
With some changes of my own thrown in (e.g. backport of r58107).
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/_abcoll.py | 544 | ||||
-rw-r--r-- | Lib/abc.py | 2 | ||||
-rw-r--r-- | Lib/collections.py | 6 | ||||
-rw-r--r-- | Lib/test/mapping_tests.py | 4 | ||||
-rwxr-xr-x | Lib/test/regrtest.py | 23 | ||||
-rw-r--r-- | Lib/test/test_abc.py | 14 | ||||
-rw-r--r-- | Lib/test/test_collections.py | 186 | ||||
-rw-r--r-- | Lib/test/test_dict.py | 4 |
8 files changed, 776 insertions, 7 deletions
diff --git a/Lib/_abcoll.py b/Lib/_abcoll.py new file mode 100644 index 0000000..ac967b2 --- /dev/null +++ b/Lib/_abcoll.py @@ -0,0 +1,544 @@ +# Copyright 2007 Google, Inc. All Rights Reserved. +# Licensed to PSF under a Contributor Agreement. + +"""Abstract Base Classes (ABCs) for collections, according to PEP 3119. + +DON'T USE THIS MODULE DIRECTLY! The classes here should be imported +via collections; they are defined here only to alleviate certain +bootstrapping issues. Unit tests are in test_collections. +""" + +from abc import ABCMeta, abstractmethod + +__all__ = ["Hashable", "Iterable", "Iterator", + "Sized", "Container", "Callable", + "Set", "MutableSet", + "Mapping", "MutableMapping", + "MappingView", "KeysView", "ItemsView", "ValuesView", + "Sequence", "MutableSequence", + ] + +### ONE-TRICK PONIES ### + +class Hashable: + __metaclass__ = ABCMeta + + @abstractmethod + def __hash__(self): + return 0 + + @classmethod + def __subclasshook__(cls, C): + if cls is Hashable: + for B in C.__mro__: + if "__hash__" in B.__dict__: + if B.__dict__["__hash__"]: + return True + break + return NotImplemented + + +class Iterable: + __metaclass__ = ABCMeta + + @abstractmethod + def __iter__(self): + while False: + yield None + + @classmethod + def __subclasshook__(cls, C): + if cls is Iterable: + if any("__iter__" in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + +Iterable.register(str) + + +class Iterator: + __metaclass__ = ABCMeta + + @abstractmethod + def __next__(self): + raise StopIteration + + def __iter__(self): + return self + + @classmethod + def __subclasshook__(cls, C): + if cls is Iterator: + if any("next" in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + + +class Sized: + __metaclass__ = ABCMeta + + @abstractmethod + def __len__(self): + return 0 + + @classmethod + def __subclasshook__(cls, C): + if cls is Sized: + if any("__len__" in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + + +class Container: + __metaclass__ = ABCMeta + + @abstractmethod + def __contains__(self, x): + return False + + @classmethod + def __subclasshook__(cls, C): + if cls is Container: + if any("__contains__" in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + + +class Callable: + __metaclass__ = ABCMeta + + @abstractmethod + def __contains__(self, x): + return False + + @classmethod + def __subclasshook__(cls, C): + if cls is Callable: + if any("__call__" in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + + +### SETS ### + + +class Set: + __metaclass__ = ABCMeta + + """A set is a finite, iterable container. + + This class provides concrete generic implementations of all + methods except for __contains__, __iter__ and __len__. + + To override the comparisons (presumably for speed, as the + semantics are fixed), all you have to do is redefine __le__ and + then the other operations will automatically follow suit. + """ + + @abstractmethod + def __contains__(self, value): + return False + + @abstractmethod + def __iter__(self): + while False: + yield None + + @abstractmethod + def __len__(self): + return 0 + + def __le__(self, other): + if not isinstance(other, Set): + return NotImplemented + if len(self) > len(other): + return False + for elem in self: + if elem not in other: + return False + return True + + def __lt__(self, other): + if not isinstance(other, Set): + return NotImplemented + return len(self) < len(other) and self.__le__(other) + + def __eq__(self, other): + if not isinstance(other, Set): + return NotImplemented + return len(self) == len(other) and self.__le__(other) + + @classmethod + def _from_iterable(cls, it): + return frozenset(it) + + def __and__(self, other): + if not isinstance(other, Iterable): + return NotImplemented + return self._from_iterable(value for value in other if value in self) + + def __or__(self, other): + if not isinstance(other, Iterable): + return NotImplemented + return self._from_iterable(itertools.chain(self, other)) + + def __sub__(self, other): + if not isinstance(other, Set): + if not isinstance(other, Iterable): + return NotImplemented + other = self._from_iterable(other) + return self._from_iterable(value for value in self + if value not in other) + + def __xor__(self, other): + if not isinstance(other, Set): + if not isinstance(other, Iterable): + return NotImplemented + other = self._from_iterable(other) + return (self - other) | (other - self) + + def _hash(self): + """Compute the hash value of a set. + + Note that we don't define __hash__: not all sets are hashable. + But if you define a hashable set type, its __hash__ should + call this function. + + This must be compatible __eq__. + + All sets ought to compare equal if they contain the same + elements, regardless of how they are implemented, and + regardless of the order of the elements; so there's not much + freedom for __eq__ or __hash__. We match the algorithm used + by the built-in frozenset type. + """ + MAX = sys.maxint + MASK = 2 * MAX + 1 + n = len(self) + h = 1927868237 * (n + 1) + h &= MASK + for x in self: + hx = hash(x) + h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167 + h &= MASK + h = h * 69069 + 907133923 + h &= MASK + if h > MAX: + h -= MASK + 1 + if h == -1: + h = 590923713 + return h + +Set.register(frozenset) + + +class MutableSet(Set): + + @abstractmethod + def add(self, value): + """Return True if it was added, False if already there.""" + raise NotImplementedError + + @abstractmethod + def discard(self, value): + """Return True if it was deleted, False if not there.""" + raise NotImplementedError + + def pop(self): + """Return the popped value. Raise KeyError if empty.""" + it = iter(self) + try: + value = it.__next__() + except StopIteration: + raise KeyError + self.discard(value) + return value + + def toggle(self, value): + """Return True if it was added, False if deleted.""" + # XXX This implementation is not thread-safe + if value in self: + self.discard(value) + return False + else: + self.add(value) + return True + + def clear(self): + """This is slow (creates N new iterators!) but effective.""" + try: + while True: + self.pop() + except KeyError: + pass + + def __ior__(self, it): + for value in it: + self.add(value) + return self + + def __iand__(self, c): + for value in self: + if value not in c: + self.discard(value) + return self + + def __ixor__(self, it): + # This calls toggle(), so if that is overridded, we call the override + for value in it: + self.toggle(it) + return self + + def __isub__(self, it): + for value in it: + self.discard(value) + return self + +MutableSet.register(set) + + +### MAPPINGS ### + + +class Mapping: + __metaclass__ = ABCMeta + + @abstractmethod + def __getitem__(self, key): + raise KeyError + + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + + def __contains__(self, key): + try: + self[key] + except KeyError: + return False + else: + return True + + @abstractmethod + def __len__(self): + return 0 + + @abstractmethod + def __iter__(self): + while False: + yield None + + def keys(self): + return KeysView(self) + + def items(self): + return ItemsView(self) + + def values(self): + return ValuesView(self) + + +class MappingView: + __metaclass__ = ABCMeta + + def __init__(self, mapping): + self._mapping = mapping + + def __len__(self): + return len(self._mapping) + + +class KeysView(MappingView, Set): + + def __contains__(self, key): + return key in self._mapping + + def __iter__(self): + for key in self._mapping: + yield key + +KeysView.register(type({}.keys())) + + +class ItemsView(MappingView, Set): + + def __contains__(self, item): + key, value = item + try: + v = self._mapping[key] + except KeyError: + return False + else: + return v == value + + def __iter__(self): + for key in self._mapping: + yield (key, self._mapping[key]) + +ItemsView.register(type({}.items())) + + +class ValuesView(MappingView): + + def __contains__(self, value): + for key in self._mapping: + if value == self._mapping[key]: + return True + return False + + def __iter__(self): + for key in self._mapping: + yield self._mapping[key] + +ValuesView.register(type({}.values())) + + +class MutableMapping(Mapping): + + @abstractmethod + def __setitem__(self, key, value): + raise KeyError + + @abstractmethod + def __delitem__(self, key): + raise KeyError + + __marker = object() + + def pop(self, key, default=__marker): + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def popitem(self): + try: + key = next(iter(self)) + except StopIteration: + raise KeyError + value = self[key] + del self[key] + return key, value + + def clear(self): + try: + while True: + self.popitem() + except KeyError: + pass + + def update(self, other=(), **kwds): + if isinstance(other, Mapping): + for key in other: + self[key] = other[key] + elif hasattr(other, "keys"): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + +MutableMapping.register(dict) + + +### SEQUENCES ### + + +class Sequence: + __metaclass__ = ABCMeta + + """All the operations on a read-only sequence. + + Concrete subclasses must override __new__ or __init__, + __getitem__, and __len__. + """ + + @abstractmethod + def __getitem__(self, index): + raise IndexError + + @abstractmethod + def __len__(self): + return 0 + + def __iter__(self): + i = 0 + while True: + try: + v = self[i] + except IndexError: + break + yield v + i += 1 + + def __contains__(self, value): + for v in self: + if v == value: + return True + return False + + def __reversed__(self): + for i in reversed(range(len(self))): + yield self[i] + + def index(self, value): + for i, v in enumerate(self): + if v == value: + return i + raise ValueError + + def count(self, value): + return sum(1 for v in self if v == value) + +Sequence.register(tuple) +Sequence.register(basestring) +Sequence.register(buffer) + + +class MutableSequence(Sequence): + + @abstractmethod + def __setitem__(self, index, value): + raise IndexError + + @abstractmethod + def __delitem__(self, index): + raise IndexError + + @abstractmethod + def insert(self, index, value): + raise IndexError + + def append(self, value): + self.insert(len(self), value) + + def reverse(self): + n = len(self) + for i in range(n//2): + self[i], self[n-i-1] = self[n-i-1], self[i] + + def extend(self, values): + for v in values: + self.append(v) + + def pop(self, index=-1): + v = self[index] + del self[index] + return v + + def remove(self, value): + del self[self.index(value)] + + def __iadd__(self, values): + self.extend(values) + +MutableSequence.register(list) @@ -69,7 +69,7 @@ class _Abstract(object): if (args or kwds) and cls.__init__ is object.__init__: raise TypeError("Can't pass arguments to __new__ " "without overriding __init__") - return object.__new__(cls) + return super(_Abstract, cls).__new__(cls) @classmethod def __subclasshook__(cls, subclass): diff --git a/Lib/collections.py b/Lib/collections.py index 7381a3a..356d961 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -5,6 +5,12 @@ from operator import itemgetter as _itemgetter from keyword import iskeyword as _iskeyword import sys as _sys +# For bootstrapping reasons, the collection ABCs are defined in _abcoll.py. +# They should however be considered an integral part of collections.py. +from _abcoll import * +import _abcoll +__all__ += _abcoll.__all__ + def namedtuple(typename, field_names, verbose=False): """Returns a new subclass of tuple with named fields. diff --git a/Lib/test/mapping_tests.py b/Lib/test/mapping_tests.py index 4b0f797..c6857ab 100644 --- a/Lib/test/mapping_tests.py +++ b/Lib/test/mapping_tests.py @@ -557,6 +557,8 @@ class TestHashMappingProtocol(TestMappingProtocol): class BadEq(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 24 d = self._empty_mapping() d[BadEq()] = 42 @@ -642,6 +644,8 @@ class TestHashMappingProtocol(TestMappingProtocol): class BadCmp(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 42 d1 = self._full_mapping({BadCmp(): 1}) d2 = self._full_mapping({1: 1}) diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py index 7973b23..9694e2a 100755 --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -648,7 +648,7 @@ def cleanup_test_droppings(testname, verbose): def dash_R(the_module, test, indirect_test, huntrleaks): # This code is hackish and inelegant, but it seems to do the job. - import copy_reg + import copy_reg, _abcoll if not hasattr(sys, 'gettotalrefcount'): raise Exception("Tracking reference leaks requires a debug build " @@ -658,6 +658,12 @@ def dash_R(the_module, test, indirect_test, huntrleaks): fs = warnings.filters[:] ps = copy_reg.dispatch_table.copy() pic = sys.path_importer_cache.copy() + abcs = {} + for abc in [getattr(_abcoll, a) for a in _abcoll.__all__]: + for obj in abc.__subclasses__() + [abc]: + abcs[obj] = obj._abc_registry.copy() + + print >> sys.stderr, abcs if indirect_test: def run_the_test(): @@ -671,12 +677,12 @@ def dash_R(the_module, test, indirect_test, huntrleaks): repcount = nwarmup + ntracked print >> sys.stderr, "beginning", repcount, "repetitions" print >> sys.stderr, ("1234567890"*(repcount//10 + 1))[:repcount] - dash_R_cleanup(fs, ps, pic) + dash_R_cleanup(fs, ps, pic, abcs) for i in range(repcount): rc = sys.gettotalrefcount() run_the_test() sys.stderr.write('.') - dash_R_cleanup(fs, ps, pic) + dash_R_cleanup(fs, ps, pic, abcs) if i >= nwarmup: deltas.append(sys.gettotalrefcount() - rc - 2) print >> sys.stderr @@ -687,11 +693,11 @@ def dash_R(the_module, test, indirect_test, huntrleaks): print >> refrep, msg refrep.close() -def dash_R_cleanup(fs, ps, pic): +def dash_R_cleanup(fs, ps, pic, abcs): import gc, copy_reg import _strptime, linecache, dircache import urlparse, urllib, urllib2, mimetypes, doctest - import struct, filecmp + import struct, filecmp, _abcoll from distutils.dir_util import _path_created # Restore some original values. @@ -701,6 +707,13 @@ def dash_R_cleanup(fs, ps, pic): sys.path_importer_cache.clear() sys.path_importer_cache.update(pic) + # Clear ABC registries, restoring previously saved ABC registries. + for abc in [getattr(_abcoll, a) for a in _abcoll.__all__]: + for obj in abc.__subclasses__() + [abc]: + obj._abc_registry = abcs.get(obj, {}).copy() + obj._abc_cache.clear() + obj._abc_negative_cache.clear() + # Clear assorted module caches. _path_created.clear() re.purge() diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 3fd9bde..c760356 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -133,6 +133,20 @@ class TestABC(unittest.TestCase): self.failUnless(issubclass(MyInt, A)) self.failUnless(isinstance(42, A)) + def test_all_new_methods_are_called(self): + class A: + __metaclass__ = abc.ABCMeta + class B: + counter = 0 + def __new__(cls): + B.counter += 1 + return super(B, cls).__new__(cls) + class C(A, B): + pass + self.assertEqual(B.counter, 0) + C() + self.assertEqual(B.counter, 1) + def test_main(): test_support.run_unittest(TestABC) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 7c5b2dc..52bae9a 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1,6 +1,12 @@ import unittest from test import test_support from collections import namedtuple +from collections import Hashable, Iterable, Iterator +from collections import Sized, Container, Callable +from collections import Set, MutableSet +from collections import Mapping, MutableMapping +from collections import Sequence, MutableSequence + class TestNamedTuple(unittest.TestCase): @@ -86,9 +92,187 @@ class TestNamedTuple(unittest.TestCase): Dot = namedtuple('Dot', 'd') self.assertEqual(Dot(1), (1,)) + +class TestOneTrickPonyABCs(unittest.TestCase): + + def test_Hashable(self): + # Check some non-hashables + non_samples = [list(), set(), dict()] + for x in non_samples: + self.failIf(isinstance(x, Hashable), repr(x)) + self.failIf(issubclass(type(x), Hashable), repr(type(x))) + # Check some hashables + samples = [None, + int(), float(), complex(), + str(), + tuple(), frozenset(), + int, list, object, type, + ] + for x in samples: + self.failUnless(isinstance(x, Hashable), repr(x)) + self.failUnless(issubclass(type(x), Hashable), repr(type(x))) + self.assertRaises(TypeError, Hashable) + # Check direct subclassing + class H(Hashable): + def __hash__(self): + return super(H, self).__hash__() + self.assertEqual(hash(H()), 0) + self.failIf(issubclass(int, H)) + + def test_Iterable(self): + # Check some non-iterables + non_samples = [None, 42, 3.14, 1j] + for x in non_samples: + self.failIf(isinstance(x, Iterable), repr(x)) + self.failIf(issubclass(type(x), Iterable), repr(type(x))) + # Check some iterables + samples = [str(), + tuple(), list(), set(), frozenset(), dict(), + dict().keys(), dict().items(), dict().values(), + (lambda: (yield))(), + (x for x in []), + ] + for x in samples: + self.failUnless(isinstance(x, Iterable), repr(x)) + self.failUnless(issubclass(type(x), Iterable), repr(type(x))) + # Check direct subclassing + class I(Iterable): + def __iter__(self): + return super(I, self).__iter__() + self.assertEqual(list(I()), []) + self.failIf(issubclass(str, I)) + + def test_Iterator(self): + non_samples = [None, 42, 3.14, 1j, "".encode('ascii'), "", (), [], + {}, set()] + for x in non_samples: + self.failIf(isinstance(x, Iterator), repr(x)) + self.failIf(issubclass(type(x), Iterator), repr(type(x))) + samples = [iter(str()), + iter(tuple()), iter(list()), iter(dict()), + iter(set()), iter(frozenset()), + iter(dict().keys()), iter(dict().items()), + iter(dict().values()), + (lambda: (yield))(), + (x for x in []), + ] + for x in samples: + self.failUnless(isinstance(x, Iterator), repr(x)) + self.failUnless(issubclass(type(x), Iterator), repr(type(x))) + + def test_Sized(self): + non_samples = [None, 42, 3.14, 1j, + (lambda: (yield))(), + (x for x in []), + ] + for x in non_samples: + self.failIf(isinstance(x, Sized), repr(x)) + self.failIf(issubclass(type(x), Sized), repr(type(x))) + samples = [str(), + tuple(), list(), set(), frozenset(), dict(), + dict().keys(), dict().items(), dict().values(), + ] + for x in samples: + self.failUnless(isinstance(x, Sized), repr(x)) + self.failUnless(issubclass(type(x), Sized), repr(type(x))) + + def test_Container(self): + non_samples = [None, 42, 3.14, 1j, + (lambda: (yield))(), + (x for x in []), + ] + for x in non_samples: + self.failIf(isinstance(x, Container), repr(x)) + self.failIf(issubclass(type(x), Container), repr(type(x))) + samples = [str(), + tuple(), list(), set(), frozenset(), dict(), + dict().keys(), dict().items(), + ] + for x in samples: + self.failUnless(isinstance(x, Container), repr(x)) + self.failUnless(issubclass(type(x), Container), repr(type(x))) + + def test_Callable(self): + non_samples = [None, 42, 3.14, 1j, + "", "".encode('ascii'), (), [], {}, set(), + (lambda: (yield))(), + (x for x in []), + ] + for x in non_samples: + self.failIf(isinstance(x, Callable), repr(x)) + self.failIf(issubclass(type(x), Callable), repr(type(x))) + samples = [lambda: None, + type, int, object, + len, + list.append, [].append, + ] + for x in samples: + self.failUnless(isinstance(x, Callable), repr(x)) + self.failUnless(issubclass(type(x), Callable), repr(type(x))) + + def test_direct_subclassing(self): + for B in Hashable, Iterable, Iterator, Sized, Container, Callable: + class C(B): + pass + self.failUnless(issubclass(C, B)) + self.failIf(issubclass(int, C)) + + def test_registration(self): + for B in Hashable, Iterable, Iterator, Sized, Container, Callable: + class C: + __metaclass__ = type + __hash__ = None # Make sure it isn't hashable by default + self.failIf(issubclass(C, B), B.__name__) + B.register(C) + self.failUnless(issubclass(C, B)) + + +class TestCollectionABCs(unittest.TestCase): + + # XXX For now, we only test some virtual inheritance properties. + # We should also test the proper behavior of the collection ABCs + # as real base classes or mix-in classes. + + def test_Set(self): + for sample in [set, frozenset]: + self.failUnless(isinstance(sample(), Set)) + self.failUnless(issubclass(sample, Set)) + + def test_MutableSet(self): + self.failUnless(isinstance(set(), MutableSet)) + self.failUnless(issubclass(set, MutableSet)) + self.failIf(isinstance(frozenset(), MutableSet)) + self.failIf(issubclass(frozenset, MutableSet)) + + def test_Mapping(self): + for sample in [dict]: + self.failUnless(isinstance(sample(), Mapping)) + self.failUnless(issubclass(sample, Mapping)) + + def test_MutableMapping(self): + for sample in [dict]: + self.failUnless(isinstance(sample(), MutableMapping)) + self.failUnless(issubclass(sample, MutableMapping)) + + def test_Sequence(self): + for sample in [tuple, list, str]: + self.failUnless(isinstance(sample(), Sequence)) + self.failUnless(issubclass(sample, Sequence)) + self.failUnless(issubclass(basestring, Sequence)) + + def test_MutableSequence(self): + for sample in [tuple, str]: + self.failIf(isinstance(sample(), MutableSequence)) + self.failIf(issubclass(sample, MutableSequence)) + for sample in [list]: + self.failUnless(isinstance(sample(), MutableSequence)) + self.failUnless(issubclass(sample, MutableSequence)) + self.failIf(issubclass(basestring, MutableSequence)) + + def test_main(verbose=None): import collections as CollectionsModule - test_classes = [TestNamedTuple] + test_classes = [TestNamedTuple, TestOneTrickPonyABCs, TestCollectionABCs] test_support.run_unittest(*test_classes) test_support.run_doctest(CollectionsModule, verbose) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 9f049ad..342e775 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -86,6 +86,8 @@ class DictTest(unittest.TestCase): class BadEq(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 24 d = {} d[BadEq()] = 42 @@ -397,6 +399,8 @@ class DictTest(unittest.TestCase): class BadCmp(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 42 d1 = {BadCmp(): 1} d2 = {1: 1} |