[Python-3000-checkins] r55911 - in python/branches/p3yk/Lib: _abcoll.py collections.py test/test_collections.py

guido.van.rossum python-3000-checkins at python.org
Mon Jun 11 22:07:58 CEST 2007


Author: guido.van.rossum
Date: Mon Jun 11 22:07:49 2007
New Revision: 55911

Added:
   python/branches/p3yk/Lib/_abcoll.py   (contents, props changed)
Modified:
   python/branches/p3yk/Lib/collections.py
   python/branches/p3yk/Lib/test/test_collections.py
Log:
Move the collections ABCs to a separate file, _abcoll.py, in order to avoid
needing to import _collections.so during the bootstrap (this will become
apparent in the next submit of os.py).

Add (plain and mutable) ABCs for Set, Mapping, Sequence.



Added: python/branches/p3yk/Lib/_abcoll.py
==============================================================================
--- (empty file)
+++ python/branches/p3yk/Lib/_abcoll.py	Mon Jun 11 22:07:49 2007
@@ -0,0 +1,535 @@
+# 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 ceratin
+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(bytes)
+
+
+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: Iterable):
+        for value in it:
+            self.add(value)
+        return self
+
+    def __iand__(self, c: Container):
+        for value in self:
+            if value not in c:
+                self.discard(value)
+        return self
+
+    def __ixor__(self, it: Iterable):
+        # 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: Iterable):
+        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)
+MutableSequence.register(bytes)

Modified: python/branches/p3yk/Lib/collections.py
==============================================================================
--- python/branches/p3yk/Lib/collections.py	(original)
+++ python/branches/p3yk/Lib/collections.py	Mon Jun 11 22:07:49 2007
@@ -1,13 +1,14 @@
-__all__ = ['deque', 'defaultdict', 'NamedTuple',
-           'Hashable', 'Iterable', 'Iterator', 'Sized', 'Container',
-           'Sequence', 'Set', 'Mapping',
-           'MutableSequence', 'MutableSet', 'MutableMapping',
-           ]
+__all__ = ['deque', 'defaultdict', 'NamedTuple']
 
 from _collections import deque, defaultdict
 from operator import itemgetter as _itemgetter
 import sys as _sys
-from abc import abstractmethod as _abstractmethod, ABCMeta as _ABCMeta
+
+# 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, s):
@@ -52,91 +53,7 @@
     return result
 
 
-class _OneTrickPony(metaclass=_ABCMeta):
-
-    """Helper class for Hashable and friends."""
-
-    @_abstractmethod
-    def __subclasshook__(self, subclass):
-        return NotImplemented
-
-
-class Hashable(_OneTrickPony):
-
-    @_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(_OneTrickPony):
-
-    @_abstractmethod
-    def __iter__(self):
-        while False:
-            yield None
-
-    @classmethod
-    def __subclasshook__(cls, C):
-        if cls is Iterable:
-            if any("__iter__" in B.__dict__ or "__getitem__" in B.__dict__
-                   for B in C.__mro__):
-                return True
-        return NotImplemented
-
-
-class Iterator(_OneTrickPony):
-
-    @_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(_OneTrickPony):
-
-    @_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(_OneTrickPony):
-
-    @_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
 
 
 if __name__ == '__main__':

Modified: python/branches/p3yk/Lib/test/test_collections.py
==============================================================================
--- python/branches/p3yk/Lib/test/test_collections.py	(original)
+++ python/branches/p3yk/Lib/test/test_collections.py	Mon Jun 11 22:07:49 2007
@@ -3,7 +3,11 @@
 import unittest
 from test import test_support
 from collections import NamedTuple
-from collections import Hashable, Iterable, Iterator, Sized, Container
+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):
@@ -55,7 +59,7 @@
         self.assertRaises(AttributeError, eval, 'p.z', locals())
 
 
-class TestABCs(unittest.TestCase):
+class TestOneTrickPonyABCs(unittest.TestCase):
 
     def test_Hashable(self):
         # Check some non-hashables
@@ -80,12 +84,6 @@
                 return super(H, self).__hash__()
         self.assertEqual(hash(H()), 0)
         self.failIf(issubclass(int, H))
-        # Check registration
-        class C:
-            __hash__ = None
-        self.failIf(issubclass(C, Hashable))
-        Hashable.register(C)
-        self.failUnless(issubclass(C, Hashable))
 
     def test_Iterable(self):
         # Check some non-iterables
@@ -109,12 +107,6 @@
                 return super(I, self).__iter__()
         self.assertEqual(list(I()), [])
         self.failIf(issubclass(str, I))
-        # Check registration
-        class C:
-            pass
-        self.failIf(issubclass(C, Iterable))
-        Iterable.register(C)
-        self.failUnless(issubclass(C, Iterable))
 
     def test_Iterator(self):
         non_samples = [None, 42, 3.14, 1j, b"", "", u"", (), [], {}, set()]
@@ -165,10 +157,86 @@
             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,
+                       "", b"", (), [], {}, 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:
+                __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, bytes, 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, bytes]:
+            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, TestABCs]
+    test_classes = [TestNamedTuple, TestOneTrickPonyABCs, TestCollectionABCs]
     test_support.run_unittest(*test_classes)
     test_support.run_doctest(CollectionsModule, verbose)
 


More information about the Python-3000-checkins mailing list