From 67ae50ee1c723db3a0d9fb22fc182dc1854fb137 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Tue, 8 Apr 2014 11:46:50 -0400 Subject: inspect: Make Signature and Parameter hashable. Issue #20334. --- Doc/library/inspect.rst | 4 ++-- Doc/whatsnew/3.5.rst | 3 ++- Lib/inspect.py | 16 ++++++++++++++++ Lib/test/test_inspect.py | 40 ++++++++++++++++++++++++++++++---------- Misc/NEWS | 2 ++ 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index f8c0317..21408f4 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -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 diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 76b65a4..7050e0a 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -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 diff --git a/Lib/inspect.py b/Lib/inspect.py index eef8dc9..4ac76b1 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -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 diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 7f1af7a..7ad190b 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -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('