Index: Objects/object.c =================================================================== --- Objects/object.c (revision 58698) +++ Objects/object.c (working copy) @@ -1902,7 +1902,7 @@ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ - 0, /*tp_hash */ + (hashfunc)_Py_HashPointer, /*tp_hash */ }; PyObject _Py_NoneStruct = { Index: Objects/typeobject.c =================================================================== --- Objects/typeobject.c (revision 58698) +++ Objects/typeobject.c (working copy) @@ -2590,12 +2590,6 @@ return f(self); } -static long -object_hash(PyObject *self) -{ - return _Py_HashPointer(self); -} - static PyObject * object_get_class(PyObject *self, void *closure) { @@ -3030,7 +3024,7 @@ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ - object_hash, /* tp_hash */ + (hashfunc)_Py_HashPointer, /* tp_hash */ 0, /* tp_call */ object_str, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ @@ -3236,6 +3230,34 @@ type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS; } +/* Map rich comparison operators to their __xx__ namesakes */ +static char *name_op[] = { + "__lt__", + "__le__", + "__eq__", + "__ne__", + "__gt__", + "__ge__", + "__cmp__", + /* These are only for overrides_hash(): */ + "__hash__", +}; + +static int +overrides_hash(PyTypeObject *type) +{ + int i; + PyObject *dict = type->tp_dict; + + assert(dict != NULL); + for (i = 0; i < 8; i++) { + if (PyDict_GetItemString(dict, name_op[i]) != NULL) + return 1; + } + return 0; +} + + static void inherit_slots(PyTypeObject *type, PyTypeObject *base) { @@ -3367,7 +3389,8 @@ if (type->tp_flags & base->tp_flags & Py_TPFLAGS_HAVE_RICHCOMPARE) { if (type->tp_compare == NULL && type->tp_richcompare == NULL && - type->tp_hash == NULL) + type->tp_hash == NULL && + !overrides_hash(type)) { type->tp_compare = base->tp_compare; type->tp_richcompare = base->tp_richcompare; @@ -3548,6 +3571,18 @@ } } + /* Hack for tp_hash and __hash__. + If after all that, tp_hash is still NULL, and __hash__ is not in + tp_dict, set tp_dict['__hash__'] equal to None. + This signals that __hash__ is not inherited. + */ + if (type->tp_hash == NULL) { + if (PyDict_GetItemString(type->tp_dict, "__hash__") == NULL) { + if (PyDict_SetItemString(type->tp_dict, "__hash__", Py_None) < 0) + goto error; + } + } + /* Some more special stuff */ base = type->tp_base; if (base != NULL) { @@ -4937,16 +4972,6 @@ return 0; } -/* Map rich comparison operators to their __xx__ namesakes */ -static char *name_op[] = { - "__lt__", - "__le__", - "__eq__", - "__ne__", - "__gt__", - "__ge__", -}; - static PyObject * half_richcompare(PyObject *self, PyObject *other, int op) { Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 58698) +++ Objects/dictobject.c (working copy) @@ -2103,13 +2103,6 @@ return dict_update_common(self, args, kwds, "dict"); } -static long -dict_nohash(PyObject *self) -{ - PyErr_SetString(PyExc_TypeError, "dict objects are unhashable"); - return -1; -} - static PyObject * dict_iter(PyDictObject *dict) { @@ -2141,7 +2134,7 @@ 0, /* tp_as_number */ &dict_as_sequence, /* tp_as_sequence */ &dict_as_mapping, /* tp_as_mapping */ - dict_nohash, /* tp_hash */ + 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ Index: Objects/listobject.c =================================================================== --- Objects/listobject.c (revision 58698) +++ Objects/listobject.c (working copy) @@ -2393,13 +2393,6 @@ return 0; } -static long -list_nohash(PyObject *self) -{ - PyErr_SetString(PyExc_TypeError, "list objects are unhashable"); - return -1; -} - static PyObject *list_iter(PyObject *seq); static PyObject *list_reversed(PyListObject* seq, PyObject* unused); @@ -2694,7 +2687,7 @@ 0, /* tp_as_number */ &list_as_sequence, /* tp_as_sequence */ &list_as_mapping, /* tp_as_mapping */ - list_nohash, /* tp_hash */ + 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ Index: Objects/setobject.c =================================================================== --- Objects/setobject.c (revision 58698) +++ Objects/setobject.c (working copy) @@ -789,13 +789,6 @@ return hash; } -static long -set_nohash(PyObject *self) -{ - PyErr_SetString(PyExc_TypeError, "set objects are unhashable"); - return -1; -} - /***** Set iterator type ***********************************************/ typedef struct { @@ -1943,7 +1936,7 @@ &set_as_number, /* tp_as_number */ &set_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ - set_nohash, /* tp_hash */ + 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ Index: Lib/_abcoll.py =================================================================== --- Lib/_abcoll.py (revision 0) +++ Lib/_abcoll.py (revision 0) @@ -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) Index: Lib/collections.py =================================================================== --- Lib/collections.py (revision 58698) +++ Lib/collections.py (working copy) @@ -1,14 +1,20 @@ -__all__ = ['deque', 'defaultdict', 'namedtuple'] +__all__ = ['deque', 'defaultdict', 'NamedTuple'] from _collections import deque, defaultdict from operator import itemgetter as _itemgetter -from keyword import iskeyword as _iskeyword import sys as _sys -def namedtuple(typename, field_names, verbose=False): +# 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, verbose=False): """Returns a new subclass of tuple with named fields. - >>> Point = namedtuple('Point', 'x y') + >>> Point = NamedTuple('Point', 'x y') >>> Point.__doc__ # docstring for the new class 'Point(x, y)' >>> p = Point(11, y=22) # instantiate with positional args or keywords @@ -19,36 +25,19 @@ (11, 22) >>> p.x + p.y # fields also accessable by name 33 - >>> d = p.__asdict__() # convert to a dictionary - >>> d['x'] - 11 - >>> Point(**d) # convert from a dictionary + >>> p # readable __repr__ with name=value style Point(x=11, y=22) >>> p.__replace__('x', 100) # __replace__() is like str.replace() but targets a named field Point(x=100, y=22) + >>> d = dict(zip(p.__fields__, p)) # use __fields__ to make a dictionary + >>> d['x'] + 11 """ - # Parse and validate the field names - if isinstance(field_names, basestring): - field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas - field_names = tuple(field_names) - for name in (typename,) + field_names: - if not name.replace('_', '').isalnum(): - raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) - if _iskeyword(name): - raise ValueError('Type names and field names cannot be a keyword: %r' % name) - if name[0].isdigit(): - raise ValueError('Type names and field names cannot start with a number: %r' % name) - seen_names = set() - for name in field_names: - if name.startswith('__') and name.endswith('__'): - raise ValueError('Field names cannot start and end with double underscores: %r' % name) - if name in seen_names: - raise ValueError('Encountered duplicate field name: %r' % name) - seen_names.add(name) - - # Create and fill-in the class template + field_names = tuple(s.replace(',', ' ').split()) # names separated by spaces and/or commas + if not ''.join((typename,) + field_names).replace('_', '').isalnum(): + raise ValueError('Type names and field names can only contain alphanumeric characters and underscores') argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes reprtxt = ', '.join('%s=%%r' % name for name in field_names) template = '''class %(typename)s(tuple): @@ -59,45 +48,31 @@ return tuple.__new__(cls, (%(argtxt)s)) def __repr__(self): return '%(typename)s(%(reprtxt)s)' %% self - def __asdict__(self, dict=dict, zip=zip): - 'Return a new dict mapping field names to their values' - return dict(zip(%(field_names)r, self)) - def __replace__(self, field, value, dict=dict, zip=zip): + def __replace__(self, field, value): 'Return a new %(typename)s object replacing one field with a new value' return %(typename)s(**dict(zip(%(field_names)r, self) + [(field, value)])) \n''' % locals() for i, name in enumerate(field_names): template += ' %s = property(itemgetter(%d))\n' % (name, i) if verbose: print template - - # Execute the template string in a temporary namespace - namespace = dict(itemgetter=_itemgetter) - try: - exec template in namespace - except SyntaxError, e: - raise SyntaxError(e.message + ':\n' + template) - result = namespace[typename] - - # For pickling to work, the __module__ variable needs to be set to the frame - # where the named tuple is created. Bypass this step in enviroments where - # sys._getframe is not defined (Jython for example). + m = dict(itemgetter=_itemgetter) + exec(template, m) + result = m[typename] if hasattr(_sys, '_getframe'): result.__module__ = _sys._getframe(1).f_globals['__name__'] - return result - if __name__ == '__main__': # verify that instances can be pickled - from cPickle import loads, dumps - Point = namedtuple('Point', 'x, y', True) + from pickle import loads, dumps + Point = NamedTuple('Point', 'x, y', True) p = Point(x=10, y=20) assert p == loads(dumps(p)) import doctest - TestResults = namedtuple('TestResults', 'failed attempted') - print TestResults(*doctest.testmod()) + TestResults = NamedTuple('TestResults', 'failed attempted') + print(TestResults(*doctest.testmod())) Index: Lib/test/regrtest.py =================================================================== --- Lib/test/regrtest.py (revision 58698) +++ Lib/test/regrtest.py (working copy) @@ -649,7 +649,7 @@ 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 " @@ -659,7 +659,13 @@ 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(): indirect_test() @@ -672,12 +678,12 @@ 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 @@ -688,11 +694,11 @@ 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. @@ -702,6 +708,13 @@ 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() Index: Lib/test/test_dict.py =================================================================== --- Lib/test/test_dict.py (revision 58698) +++ Lib/test/test_dict.py (working copy) @@ -86,6 +86,8 @@ class BadEq(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 24 d = {} d[BadEq()] = 42 @@ -393,6 +395,8 @@ class BadCmp(object): def __eq__(self, other): raise Exc() + def __hash__(self): + return 42 d1 = {BadCmp(): 1} d2 = {1: 1} Index: Lib/test/mapping_tests.py =================================================================== --- Lib/test/mapping_tests.py (revision 58698) +++ Lib/test/mapping_tests.py (working copy) @@ -557,6 +557,8 @@ 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 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}) Index: Lib/test/test_collections.py =================================================================== --- Lib/test/test_collections.py (revision 58698) +++ Lib/test/test_collections.py (working copy) @@ -1,31 +1,30 @@ +"""Unit tests for collections.py.""" + import unittest from test import test_support -from collections import namedtuple +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): def test_factory(self): - Point = namedtuple('Point', 'x y') + Point = NamedTuple('Point', 'x y') self.assertEqual(Point.__name__, 'Point') self.assertEqual(Point.__doc__, 'Point(x, y)') self.assertEqual(Point.__slots__, ()) self.assertEqual(Point.__module__, __name__) self.assertEqual(Point.__getitem__, tuple.__getitem__) + self.assertRaises(ValueError, NamedTuple, 'abc%', 'def ghi') + self.assertRaises(ValueError, NamedTuple, 'abc', 'def g%hi') + NamedTuple('Point0', 'x1 y2') # Verify that numbers are allowed in names - self.assertRaises(ValueError, namedtuple, 'abc%', 'efg ghi') # type has non-alpha char - self.assertRaises(ValueError, namedtuple, 'class', 'efg ghi') # type has keyword - self.assertRaises(ValueError, namedtuple, '9abc', 'efg ghi') # type starts with digit - - self.assertRaises(ValueError, namedtuple, 'abc', 'efg g%hi') # field with non-alpha char - self.assertRaises(ValueError, namedtuple, 'abc', 'abc class') # field has keyword - self.assertRaises(ValueError, namedtuple, 'abc', '8efg 9ghi') # field starts with digit - self.assertRaises(ValueError, namedtuple, 'abc', '__efg__ ghi') # field with double underscores - self.assertRaises(ValueError, namedtuple, 'abc', 'efg efg ghi') # duplicate field - - namedtuple('Point0', 'x1 y2') # Verify that numbers are allowed in names - def test_instance(self): - Point = namedtuple('Point', 'x y') + Point = NamedTuple('Point', 'x y') p = Point(11, 22) self.assertEqual(p, Point(x=11, y=22)) self.assertEqual(p, Point(11, y=22)) @@ -41,20 +40,14 @@ self.assert_('__weakref__' not in dir(p)) self.assertEqual(p.__fields__, ('x', 'y')) # test __fields__ attribute self.assertEqual(p.__replace__('x', 1), (1, 22)) # test __replace__ method - self.assertEqual(p.__asdict__(), dict(x=11, y=22)) # test __dict__ method # verify that field string can have commas - Point = namedtuple('Point', 'x, y') + Point = NamedTuple('Point', 'x, y') p = Point(x=11, y=22) self.assertEqual(repr(p), 'Point(x=11, y=22)') - # verify that fieldspec can be a non-string sequence - Point = namedtuple('Point', ('x', 'y')) - p = Point(x=11, y=22) - self.assertEqual(repr(p), 'Point(x=11, y=22)') - def test_tupleness(self): - Point = namedtuple('Point', 'x y') + Point = NamedTuple('Point', 'x y') p = Point(11, 22) self.assert_(isinstance(p, tuple)) @@ -72,17 +65,196 @@ self.assertEqual(p.y, y) self.assertRaises(AttributeError, eval, 'p.z', locals()) + +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_odd_sizes(self): - Zero = namedtuple('Zero', '') + Zero = NamedTuple('Zero', '') self.assertEqual(Zero(), ()) - Dot = namedtuple('Dot', 'd') + Dot = NamedTuple('Dot', 'd') self.assertEqual(Dot(1), (1,)) 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) + if __name__ == "__main__": test_main(verbose=True)