[Python-checkins] r54638 - sandbox/trunk/abc/abc.py sandbox/trunk/abc/test_abc.py

guido.van.rossum python-checkins at python.org
Sat Mar 31 22:27:52 CEST 2007


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()


More information about the Python-checkins mailing list