]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
inspect: Make Signature and Parameter hashable. Issue #20334.
authorYury Selivanov <yselivanov@sprymix.com>
Tue, 8 Apr 2014 15:46:50 +0000 (11:46 -0400)
committerYury Selivanov <yselivanov@sprymix.com>
Tue, 8 Apr 2014 15:46:50 +0000 (11:46 -0400)
Doc/library/inspect.rst
Doc/whatsnew/3.5.rst
Lib/inspect.py
Lib/test/test_inspect.py
Misc/NEWS

index f8c0317735027590ba607f2c41c9f75ae51e148b..21408f46645bd97ab22703c0beff4097daccc56d 100644 (file)
@@ -463,7 +463,7 @@ function.
    modified copy.
 
    .. versionchanged:: 3.5
-      Signature objects are picklable.
+      Signature objects are picklable and hashable.
 
    .. attribute:: Signature.empty
 
@@ -530,7 +530,7 @@ function.
    you can use :meth:`Parameter.replace` to create a modified copy.
 
    .. versionchanged:: 3.5
-      Parameter objects are picklable.
+      Parameter objects are picklable and hashable.
 
    .. attribute:: Parameter.empty
 
index 76b65a414774d098d93290ab74741cb129d260ce..7050e0ad16e0c4efcb2fd7e27a715491284f984f 100644 (file)
@@ -143,7 +143,8 @@ Improved Modules
   (contributed by Claudiu Popa in :issue:`20627`).
 
 * :class:`inspect.Signature` and :class:`inspect.Parameter` are now
-  picklable (contributed by Yury Selivanov in :issue:`20726`).
+  picklable and hashable (contributed by Yury Selivanov in :issue:`20726`
+  and :issue:`20334`).
 
 * New class method :meth:`inspect.Signature.from_callable`, which makes
   subclassing of :class:`~inspect.Signature` easier (contributed
index eef8dc90ac4a56f99185298acfa1aa087afa422a..4ac76b1f51f582bc5f2e11a2d92992fb0acf76f0 100644 (file)
@@ -2231,6 +2231,16 @@ class Parameter:
         return '<{} at {:#x} "{}">'.format(self.__class__.__name__,
                                            id(self), self)
 
+    def __hash__(self):
+        hash_tuple = (self.name, int(self.kind))
+
+        if self._annotation is not _empty:
+            hash_tuple += (self._annotation,)
+        if self._default is not _empty:
+            hash_tuple += (self._default,)
+
+        return hash(hash_tuple)
+
     def __eq__(self, other):
         return (issubclass(other.__class__, Parameter) and
                 self._name == other._name and
@@ -2524,6 +2534,12 @@ class Signature:
         return type(self)(parameters,
                           return_annotation=return_annotation)
 
+    def __hash__(self):
+        hash_tuple = tuple(self.parameters.values())
+        if self._return_annotation is not _empty:
+            hash_tuple += (self._return_annotation,)
+        return hash(hash_tuple)
+
     def __eq__(self, other):
         if (not issubclass(type(other), Signature) or
                     self.return_annotation != other.return_annotation or
index 7f1af7a46d93695da26b9a026a58e8454c96ebbe..7ad190b0562838b806f97243a32ba210d247ab98 100644 (file)
@@ -2513,11 +2513,29 @@ class TestSignatureObject(unittest.TestCase):
         def bar(pos, *args, c, b, a=42, **kwargs:int): pass
         self.assertEqual(inspect.signature(foo), inspect.signature(bar))
 
-    def test_signature_unhashable(self):
+    def test_signature_hashable(self):
+        S = inspect.Signature
+        P = inspect.Parameter
+
         def foo(a): pass
-        sig = inspect.signature(foo)
+        foo_sig = inspect.signature(foo)
+
+        manual_sig = S(parameters=[P('a', P.POSITIONAL_OR_KEYWORD)])
+
+        self.assertEqual(hash(foo_sig), hash(manual_sig))
+        self.assertNotEqual(hash(foo_sig),
+                            hash(manual_sig.replace(return_annotation='spam')))
+
+        def bar(a) -> 1: pass
+        self.assertNotEqual(hash(foo_sig), hash(inspect.signature(bar)))
+
+        def foo(a={}): pass
         with self.assertRaisesRegex(TypeError, 'unhashable type'):
-            hash(sig)
+            hash(inspect.signature(foo))
+
+        def foo(a) -> {}: pass
+        with self.assertRaisesRegex(TypeError, 'unhashable type'):
+            hash(inspect.signature(foo))
 
     def test_signature_str(self):
         def foo(a:int=1, *, b, c=None, **kwargs) -> 42:
@@ -2651,6 +2669,15 @@ class TestParameterObject(unittest.TestCase):
         self.assertTrue(repr(p).startswith('<Parameter'))
         self.assertTrue('"a=42"' in repr(p))
 
+    def test_signature_parameter_hashable(self):
+        P = inspect.Parameter
+        foo = P('foo', kind=P.POSITIONAL_ONLY)
+        self.assertEqual(hash(foo), hash(P('foo', kind=P.POSITIONAL_ONLY)))
+        self.assertNotEqual(hash(foo), hash(P('foo', kind=P.POSITIONAL_ONLY,
+                                              default=42)))
+        self.assertNotEqual(hash(foo),
+                            hash(foo.replace(kind=P.VAR_POSITIONAL)))
+
     def test_signature_parameter_equality(self):
         P = inspect.Parameter
         p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY)
@@ -2661,13 +2688,6 @@ class TestParameterObject(unittest.TestCase):
         self.assertEqual(p, P('foo', default=42,
                               kind=inspect.Parameter.KEYWORD_ONLY))
 
-    def test_signature_parameter_unhashable(self):
-        p = inspect.Parameter('foo', default=42,
-                              kind=inspect.Parameter.KEYWORD_ONLY)
-
-        with self.assertRaisesRegex(TypeError, 'unhashable type'):
-            hash(p)
-
     def test_signature_parameter_replace(self):
         p = inspect.Parameter('foo', default=42,
                               kind=inspect.Parameter.KEYWORD_ONLY)
index 023f81a0b2a8131bdd6f3104064e9c495cbebd2c..fd445906bd83fd454883be0255addc0ffbe0574a 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -154,6 +154,8 @@ Library
   positional-or-keyword arguments passed as keyword arguments become
   keyword-only.
 
+- Issue #20334: inspect.Signature and inspect.Parameter are now hashable.
+
 IDLE
 ----