r54638 - sandbox/trunk/abc/abc.py sandbox/trunk/abc/test_abc.py
Author: guido.van.rossum Date: Sat Mar 31 22:27:51 2007 New Revision: 54638 Added: sandbox/trunk/abc/test_abc.py (contents, props changed) Modified: sandbox/trunk/abc/abc.py Log: Create some unit tests. Add some docs to the abstractmethod decorator. Modified: sandbox/trunk/abc/abc.py ============================================================================== --- sandbox/trunk/abc/abc.py (original) +++ sandbox/trunk/abc/abc.py Sat Mar 31 22:27:51 2007 @@ -3,8 +3,8 @@ """Abstract Base Classes experiment. Note: this depends on the brand new Py3k feature that object.__ne__() -is implemented by calling object.__eq__() and reversing the outcome -(unless NotImplemented). +is implemented by calling __eq__() and reversing the outcome (unless +NotImplemented). XXX Should we use argument annotations here? @@ -18,27 +18,73 @@ def abstractmethod(funcobj): - """A decorator indicating abstract methods.""" + """A decorator indicating abstract methods. + + Requires that the class (directly or indirectly) derives from + Abstract, and that the metaclass is AbstractClass or derived from it + (deriving from Abstract ensure this). A class deriving from + Abstract cannot be instantiated unless all of its abstract methods + are overridden. The abstract methods can be called using any of the + the normal 'super' call mechanisms. + + Usage: + + class C(Abstract): + @abstractmethod + def my_abstract_method(self, ...): + ... + + When combining this with other decorators, this should come last: + + class C(Abstract): + @classmethod + @abstractmethod + def my_abstract_class_method(self, ...): + ... + """ funcobj.__abstractmethod__ = True return funcobj class AbstractClass(type): + """Metaclass to support the abstractmethod decorator.""" + def __new__(mcls, name, bases, namespace): - obj = super(AbstractClass, mcls).__new__(mcls, name, bases, namespace) + cls = super(AbstractClass, mcls).__new__(mcls, name, bases, namespace) abstracts = set() for base in bases: abstracts.update(getattr(base, "__abstractmethods__", set())) for name, value in namespace.items(): if getattr(value, "__abstractmethod__", False): abstracts.add(name) - obj.__abstractmethods__ = abstracts - return obj + cls.__abstractmethods__ = abstracts + return cls + + +class AbstractInstantiationError(TypeError): + + """Exception raised when an abstract class is instantiated.""" + + def __init__(self, abstract_methods): + TypeError.__init__(self) + self.abstract_methods = abstract_methods + + def __str__(self): + msg = ", ".join(sorted(self.abstract_methods)) + return "Can't instantiate class with abstract method(s) %s" % msg + + def __repr__(self): + return "AbstractInstantiationError(%r)" % (self.abstract_methods,) class Abstract(metaclass=AbstractClass): + """Base class to support the abstractmethod decorator. + + This implicitly sets the metaclass to AbstractClass. + """ + def __new__(cls): bad = set() for name in cls.__abstractmethods__: @@ -46,8 +92,7 @@ if getattr(value, "__abstractmethod__", False): bad.add(name) if bad: - raise TypeError("Can't instantiate class with abstract methods %s" % - ", ".join(sorted(bad))) + raise AbstractInstantiationError(bad) return super(Abstract, cls).__new__(cls) @@ -499,29 +544,3 @@ if h == -1: h = -2 return h - - -### test ### - - -def _test(): - # Test that HashableSequence.__hash__() emulates tuple.__hash__(). - class C(HashableSequence): - def __new__(cls, values): - obj = super(C, cls).__new__(cls) - obj.__values = list(values) - return obj - def __len__(self): - return len(self.__values) - def __getitem__(self, i): - return self.__values[i] - for l in ([], [0], [1], [0, 1], - list(range(-sys.maxint, sys.maxint, 100000)), - "The quick brown fox jumps over the lazy dog".split()): - a = C(l) - ha = hash(a) - htl = hash(tuple(l)) - assert ha == htl, (l, ha, htl) - -if __name__ == "__main__": - _test() Added: sandbox/trunk/abc/test_abc.py ============================================================================== --- (empty file) +++ sandbox/trunk/abc/test_abc.py Sat Mar 31 22:27:51 2007 @@ -0,0 +1,43 @@ +#!/usr/bin/env python3.0 +"""Unit tests for abc.py.""" + +import sys +import unittest + +import abc + +class ABCTestCase(unittest.TestCase): + + def test_abstract_method_machinery(self): + class C(abc.Abstract): + @abc.abstractmethod + def foo(self): pass + def bar(self): pass + self.assertRaises(abc.AbstractInstantiationError, C) + class D(C): + def bar(self): pass + self.assertRaises(abc.AbstractInstantiationError, D) + class E(D): + def foo(self): pass + E() + + def test_hashable_sequence_hash_matches_tuple_hash(self): + class C(abc.HashableSequence): + def __new__(cls, values): + obj = super(C, cls).__new__(cls) + obj.__values = list(values) + return obj + def __len__(self): + return len(self.__values) + def __getitem__(self, i): + return self.__values[i] + for l in ([], [0], [1], [0, 1], + list(range(-sys.maxint, sys.maxint, 100000)), + "The quick brown fox jumps over the lazy dog".split()): + hcl = hash(C(l)) + htl = hash(tuple(l)) + self.assertEqual(hcl, htl, repr((l, hcl, htl))) + + +if __name__ == "__main__": + unittest.main()
participants (1)
-
guido.van.rossum