inspect: Make Signature and Parameter hashable. Issue #20334.
This commit is contained in:
parent
3f73ca23cf
commit
67ae50ee1c
|
@ -463,7 +463,7 @@ function.
|
||||||
modified copy.
|
modified copy.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
Signature objects are picklable.
|
Signature objects are picklable and hashable.
|
||||||
|
|
||||||
.. attribute:: Signature.empty
|
.. attribute:: Signature.empty
|
||||||
|
|
||||||
|
@ -530,7 +530,7 @@ function.
|
||||||
you can use :meth:`Parameter.replace` to create a modified copy.
|
you can use :meth:`Parameter.replace` to create a modified copy.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
Parameter objects are picklable.
|
Parameter objects are picklable and hashable.
|
||||||
|
|
||||||
.. attribute:: Parameter.empty
|
.. attribute:: Parameter.empty
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,8 @@ Improved Modules
|
||||||
(contributed by Claudiu Popa in :issue:`20627`).
|
(contributed by Claudiu Popa in :issue:`20627`).
|
||||||
|
|
||||||
* :class:`inspect.Signature` and :class:`inspect.Parameter` are now
|
* :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
|
* New class method :meth:`inspect.Signature.from_callable`, which makes
|
||||||
subclassing of :class:`~inspect.Signature` easier (contributed
|
subclassing of :class:`~inspect.Signature` easier (contributed
|
||||||
|
|
|
@ -2231,6 +2231,16 @@ class Parameter:
|
||||||
return '<{} at {:#x} "{}">'.format(self.__class__.__name__,
|
return '<{} at {:#x} "{}">'.format(self.__class__.__name__,
|
||||||
id(self), self)
|
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):
|
def __eq__(self, other):
|
||||||
return (issubclass(other.__class__, Parameter) and
|
return (issubclass(other.__class__, Parameter) and
|
||||||
self._name == other._name and
|
self._name == other._name and
|
||||||
|
@ -2524,6 +2534,12 @@ class Signature:
|
||||||
return type(self)(parameters,
|
return type(self)(parameters,
|
||||||
return_annotation=return_annotation)
|
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):
|
def __eq__(self, other):
|
||||||
if (not issubclass(type(other), Signature) or
|
if (not issubclass(type(other), Signature) or
|
||||||
self.return_annotation != other.return_annotation or
|
self.return_annotation != other.return_annotation or
|
||||||
|
|
|
@ -2513,11 +2513,29 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
def bar(pos, *args, c, b, a=42, **kwargs:int): pass
|
def bar(pos, *args, c, b, a=42, **kwargs:int): pass
|
||||||
self.assertEqual(inspect.signature(foo), inspect.signature(bar))
|
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
|
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'):
|
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 test_signature_str(self):
|
||||||
def foo(a:int=1, *, b, c=None, **kwargs) -> 42:
|
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(repr(p).startswith('<Parameter'))
|
||||||
self.assertTrue('"a=42"' in repr(p))
|
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):
|
def test_signature_parameter_equality(self):
|
||||||
P = inspect.Parameter
|
P = inspect.Parameter
|
||||||
p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY)
|
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,
|
self.assertEqual(p, P('foo', default=42,
|
||||||
kind=inspect.Parameter.KEYWORD_ONLY))
|
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):
|
def test_signature_parameter_replace(self):
|
||||||
p = inspect.Parameter('foo', default=42,
|
p = inspect.Parameter('foo', default=42,
|
||||||
kind=inspect.Parameter.KEYWORD_ONLY)
|
kind=inspect.Parameter.KEYWORD_ONLY)
|
||||||
|
|
|
@ -154,6 +154,8 @@ Library
|
||||||
positional-or-keyword arguments passed as keyword arguments become
|
positional-or-keyword arguments passed as keyword arguments become
|
||||||
keyword-only.
|
keyword-only.
|
||||||
|
|
||||||
|
- Issue #20334: inspect.Signature and inspect.Parameter are now hashable.
|
||||||
|
|
||||||
IDLE
|
IDLE
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue