From: Serhiy Storchaka Date: Sat, 18 Jul 2015 20:20:50 +0000 (+0300) Subject: Issue #24206: Fixed __eq__ and __ne__ methods of inspect classes. X-Git-Tag: v3.5.0b4~38 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2489bd5d4e45b2a1d90f9336bf528ab1bba2d796;p=python Issue #24206: Fixed __eq__ and __ne__ methods of inspect classes. --- 2489bd5d4e45b2a1d90f9336bf528ab1bba2d796 diff --cc Lib/inspect.py index 45679cfc0b,4298de6b60..42f24cddba --- a/Lib/inspect.py +++ b/Lib/inspect.py @@@ -2482,21 -2202,16 +2482,20 @@@ class Parameter return formatted def __repr__(self): - return '<{} at {:#x} {!r}>'.format(self.__class__.__name__, - id(self), self.name) + return '<{} "{}">'.format(self.__class__.__name__, self) + + def __hash__(self): + return hash((self.name, self.kind, self.annotation, self.default)) def __eq__(self, other): - return (self is other or - (issubclass(other.__class__, Parameter) and - self._name == other._name and - self._kind == other._kind and - self._default == other._default and - self._annotation == other._annotation)) - - def __ne__(self, other): - return not self.__eq__(other) ++ if self is other: ++ return True + if not isinstance(other, Parameter): + return NotImplemented + return (self._name == other._name and + self._kind == other._kind and + self._default == other._default and + self._annotation == other._annotation) class BoundArguments: @@@ -2579,61 -2292,15 +2578,60 @@@ return kwargs + def apply_defaults(self): + """Set default values for missing arguments. + + For variable-positional arguments (*args) the default is an + empty tuple. + + For variable-keyword arguments (**kwargs) the default is an + empty dict. + """ + arguments = self.arguments + if not arguments: + return + new_arguments = [] + for name, param in self._signature.parameters.items(): + try: + new_arguments.append((name, arguments[name])) + except KeyError: + if param.default is not _empty: + val = param.default + elif param.kind is _VAR_POSITIONAL: + val = () + elif param.kind is _VAR_KEYWORD: + val = {} + else: + # This BoundArguments was likely produced by + # Signature.bind_partial(). + continue + new_arguments.append((name, val)) + self.arguments = OrderedDict(new_arguments) + def __eq__(self, other): - return (self is other or - (issubclass(other.__class__, BoundArguments) and - self.signature == other.signature and - self.arguments == other.arguments)) - - def __ne__(self, other): - return not self.__eq__(other) ++ if self is other: ++ return True + if not isinstance(other, BoundArguments): + return NotImplemented + return (self.signature == other.signature and + self.arguments == other.arguments) + def __setstate__(self, state): + self._signature = state['_signature'] + self.arguments = state['arguments'] + + def __getstate__(self): + return {'_signature': self._signature, 'arguments': self.arguments} + + def __repr__(self): + args = [] + for arg, value in self.arguments.items(): + args.append('{}={!r}'.format(arg, value)) + return '<{} ({})>'.format(self.__class__.__name__, ', '.join(args)) + class Signature: - '''A Signature object represents the overall signature of a function. + """A Signature object represents the overall signature of a function. It stores a Parameter object for each parameter accepted by the function, as well as information specific to the function itself. @@@ -2760,30 -2488,39 +2758,29 @@@ return type(self)(parameters, return_annotation=return_annotation) - def __eq__(self, other): - if not isinstance(other, Signature): - return NotImplemented - if (self.return_annotation != other.return_annotation or - len(self.parameters) != len(other.parameters)): - return False + def _hash_basis(self): + params = tuple(param for param in self.parameters.values() + if param.kind != _KEYWORD_ONLY) - other_positions = {param: idx - for idx, param in enumerate(other.parameters.keys())} + kwo_params = {param.name: param for param in self.parameters.values() + if param.kind == _KEYWORD_ONLY} - for idx, (param_name, param) in enumerate(self.parameters.items()): - if param.kind == _KEYWORD_ONLY: - try: - other_param = other.parameters[param_name] - except KeyError: - return False - else: - if param != other_param: - return False - else: - try: - other_idx = other_positions[param_name] - except KeyError: - return False - else: - if (idx != other_idx or - param != other.parameters[param_name]): - return False + return params, kwo_params, self.return_annotation - return True + def __hash__(self): + params, kwo_params, return_annotation = self._hash_basis() + kwo_params = frozenset(kwo_params.values()) + return hash((params, kwo_params, return_annotation)) + + def __eq__(self, other): - return (self is other or - (isinstance(other, Signature) and - self._hash_basis() == other._hash_basis())) - - def __ne__(self, other): - return not self.__eq__(other) ++ if self is other: ++ return True ++ if not isinstance(other, Signature): ++ return NotImplemented ++ return self._hash_basis() == other._hash_basis() def _bind(self, args, kwargs, *, partial=False): - '''Private method. Don't use directly.''' + """Private method. Don't use directly.""" arguments = OrderedDict() diff --cc Lib/test/test_inspect.py index a02f2e1b60,1a124b5db2..8e0e73cf00 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@@ -81,16 -76,19 +81,19 @@@ def generator_function_example(self) for i in range(2): yield i +async def coroutine_function_example(self): + return 'spam' + +@types.coroutine +def gen_coroutine_function_example(self): + yield + return 'spam' + + class EqualsToAll: + def __eq__(self, other): + return True class TestPredicates(IsTestBase): - def test_sixteen(self): - count = len([x for x in dir(inspect) if x.startswith('is')]) - # This test is here for remember you to update Doc/library/inspect.rst - # which claims there are 16 such functions - expected = 16 - err_msg = "There are %d (not %d) is* functions" % (count, expected) - self.assertEqual(count, expected, err_msg) - def test_excluding_predicates(self): global tb @@@ -2672,95 -2463,68 +2675,110 @@@ class TestSignatureObject(unittest.Test def test_signature_equality(self): def foo(a, *, b:int) -> float: pass - self.assertNotEqual(inspect.signature(foo), 42) + self.assertFalse(inspect.signature(foo) == 42) + self.assertTrue(inspect.signature(foo) != 42) + self.assertTrue(inspect.signature(foo) == EqualsToAll()) + self.assertFalse(inspect.signature(foo) != EqualsToAll()) def bar(a, *, b:int) -> float: pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, b:int) -> int: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, b:int): pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, b:int=42) -> float: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, c) -> float: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, b:int) -> float: pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def spam(b:int, a) -> float: pass - self.assertNotEqual(inspect.signature(spam), inspect.signature(bar)) + self.assertFalse(inspect.signature(spam) == inspect.signature(bar)) + self.assertTrue(inspect.signature(spam) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(spam)), hash(inspect.signature(bar))) def foo(*, a, b, c): pass def bar(*, c, b, a): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(*, a=1, b, c): pass def bar(*, c, b, a=1): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(pos, *, a=1, b, c): pass def bar(pos, *, c, b, a=1): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(pos, *, a, b, c): pass def bar(pos, *, c, b, a=1): pass - self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(pos, *args, a=42, b, c, **kwargs:int): pass def bar(pos, *args, c, b, a=42, **kwargs:int): pass - self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) + self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def test_signature_hashable(self): + S = inspect.Signature + P = inspect.Parameter - def test_signature_unhashable(self): 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: @@@ -2907,12 -2648,25 +2925,18 @@@ class TestParameterObject(unittest.Test P = inspect.Parameter p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY) - self.assertEqual(p, p) - self.assertNotEqual(p, 42) + self.assertTrue(p == p) + self.assertFalse(p != p) + self.assertFalse(p == 42) + self.assertTrue(p != 42) + self.assertTrue(p == EqualsToAll()) + self.assertFalse(p != EqualsToAll()) - self.assertEqual(p, P('foo', default=42, - kind=inspect.Parameter.KEYWORD_ONLY)) + self.assertTrue(p == P('foo', default=42, + kind=inspect.Parameter.KEYWORD_ONLY)) + self.assertFalse(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) @@@ -3227,65 -2979,9 +3257,67 @@@ class TestBoundArguments(unittest.TestC def bar(b): pass ba4 = inspect.signature(bar).bind(1) - self.assertNotEqual(ba, ba4) + self.assertFalse(ba == ba4) + self.assertTrue(ba != ba4) + def foo(*, a, b): pass + sig = inspect.signature(foo) + ba1 = sig.bind(a=1, b=2) + ba2 = sig.bind(b=2, a=1) - self.assertEqual(ba1, ba2) ++ self.assertTrue(ba1 == ba2) ++ self.assertFalse(ba1 != ba2) + + def test_signature_bound_arguments_pickle(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + sig = inspect.signature(foo) + ba = sig.bind(20, 30, z={}) + + for ver in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(pickle_ver=ver): + ba_pickled = pickle.loads(pickle.dumps(ba, ver)) + self.assertEqual(ba, ba_pickled) + + def test_signature_bound_arguments_repr(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + sig = inspect.signature(foo) + ba = sig.bind(20, 30, z={}) + self.assertRegex(repr(ba), r'') + + def test_signature_bound_arguments_apply_defaults(self): + def foo(a, b=1, *args, c:1={}, **kw): pass + sig = inspect.signature(foo) + + ba = sig.bind(20) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 20), ('b', 1), ('args', ()), ('c', {}), ('kw', {})]) + + # Make sure that we preserve the order: + # i.e. 'c' should be *before* 'kw'. + ba = sig.bind(10, 20, 30, d=1) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 10), ('b', 20), ('args', (30,)), ('c', {}), ('kw', {'d':1})]) + + # Make sure that BoundArguments produced by bind_partial() + # are supported. + def foo(a, b): pass + sig = inspect.signature(foo) + ba = sig.bind_partial(20) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 20)]) + + # Test no args + def foo(): pass + sig = inspect.signature(foo) + ba = sig.bind() + ba.apply_defaults() + self.assertEqual(list(ba.arguments.items()), []) + class TestSignaturePrivateHelpers(unittest.TestCase): def test_signature_get_bound_param(self): diff --cc Misc/NEWS index ba01a06ac6,ad5fef33fa..b400a91211 --- a/Misc/NEWS +++ b/Misc/NEWS @@@ -10,71 -10,78 +10,73 @@@ Release date: 2015-07-2 Core and Builtins ----------------- -- Issue #24467: Fixed possible buffer over-read in bytearray. The bytearray - object now always allocates place for trailing null byte and it's buffer now - is always null-terminated. - -- Issue #24115: Update uses of PyObject_IsTrue(), PyObject_Not(), - PyObject_IsInstance(), PyObject_RichCompareBool() and _PyDict_Contains() - to check for and handle errors correctly. - -- Issue #24257: Fixed system error in the comparison of faked - types.SimpleNamespace. +- Issue #24569: Make PEP 448 dictionary evaluation more consistent. -- Issue #22939: Fixed integer overflow in iterator object. Patch by - Clement Rouault. +- Issue #24583: Fix crash when set is mutated while being updated. -- Issue #23985: Fix a possible buffer overrun when deleting a slice from - the front of a bytearray and then appending some other bytes data. +- Issue #24407: Fix crash when dict is mutated while being updated. -- Issue #24102: Fixed exception type checking in standard error handlers. +Library +------- -- Issue #23757: PySequence_Tuple() incorrectly called the concrete list API - when the data was a list subclass. ++- Issue #24206: Fixed __eq__ and __ne__ methods of inspect classes. + -- Issue #24407: Fix crash when dict is mutated while being updated. +- Issue #24631: Fixed regression in the timeit module with multiline setup. -- Issue #24096: Make warnings.warn_explicit more robust against mutation of the - warnings.filters list. +- Issue #18622: unittest.mock.mock_open().reset_mock would recurse infinitely. + Patch from Nicola Palumbo and Laurent De Buyst. -- Issue #23996: Avoid a crash when a delegated generator raises an - unnormalized StopIteration exception. Patch by Stefan Behnel. +- Issue #23661: unittest.mock side_effects can now be exceptions again. This + was a regression vs Python 3.4. Patch from Ignacio Rossi -- Issue #24022: Fix tokenizer crash when processing undecodable source code. +- Issue #24608: chunk.Chunk.read() now always returns bytes, not str. -- Issue #23309: Avoid a deadlock at shutdown if a daemon thread is aborted - while it is holding a lock to a buffered I/O object, and the main thread - tries to use the same I/O object (typically stdout or stderr). A fatal - error is emitted instead. +- Issue #18684: Fixed reading out of the buffer in the re module. -- Issue #22977: Fixed formatting Windows error messages on Wine. - Patch by Martin Panter. +- Issue #24259: tarfile now raises a ReadError if an archive is truncated + inside a data segment. -- Issue #23803: Fixed str.partition() and str.rpartition() when a separator - is wider then partitioned string. +- Issue #15014: SMTP.auth() and SMTP.login() now support RFC 4954's optional + initial-response argument to the SMTP AUTH command. -- Issue #23192: Fixed generator lambdas. Patch by Bruno Cauet. -- Issue #23629: Fix the default __sizeof__ implementation for variable-sized - objects. +What's New in Python 3.5.0 beta 3? +================================== -- Issue #24044: Fix possible null pointer dereference in list.sort in out of - memory conditions. +Release date: 2015-07-05 -- Issue #21354: PyCFunction_New function is exposed by python DLL again. +Core and Builtins +----------------- -Library -------- +- Issue #24467: Fixed possible buffer over-read in bytearray. The bytearray + object now always allocates place for trailing null byte and it's buffer now + is always null-terminated. -- Issue #24206: Fixed __eq__ and __ne__ methods of inspect classes. +- Upgrade to Unicode 8.0.0. -- Issue #21750: mock_open.read_data can now be read from each instance, as it - could in Python 3.3. +- Issue #24345: Add Py_tp_finalize slot for the stable ABI. -- Issue #23247: Fix a crash in the StreamWriter.reset() of CJK codecs. +- Issue #24400: Introduce a distinct type for PEP 492 coroutines; add + types.CoroutineType, inspect.getcoroutinestate, inspect.getcoroutinelocals; + coroutines no longer use CO_GENERATOR flag; sys.set_coroutine_wrapper + works only for 'async def' coroutines; inspect.iscoroutine no longer + uses collections.abc.Coroutine, it's intended to test for pure 'async def' + coroutines only; add new opcode: GET_YIELD_FROM_ITER; fix generators wrapper + used in types.coroutine to be instance of collections.abc.Generator; + collections.abc.Awaitable and collections.abc.Coroutine can no longer + be used to detect generator-based coroutines--use inspect.isawaitable + instead. -- Issue #18622: unittest.mock.mock_open().reset_mock would recurse infinitely. - Patch from Nicola Palumbo and Laurent De Buyst. +- Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines. + Contributed by Benno Leslie and Yury Selivanov. -- Issue #24608: chunk.Chunk.read() now always returns bytes, not str. +- Issue #19235: Add new RecursionError exception. Patch by Georg Brandl. -- Issue #18684: Fixed reading out of the buffer in the re module. +Library +------- -- Issue #24259: tarfile now raises a ReadError if an archive is truncated - inside a data segment. +- Issue #21750: mock_open.read_data can now be read from each instance, as it + could in Python 3.3. - Issue #24552: Fix use after free in an error case of the _pickle module.