]> granicus.if.org Git - python/commitdiff
Issue #24206: Fixed __eq__ and __ne__ methods of inspect classes.
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 18 Jul 2015 20:20:50 +0000 (23:20 +0300)
committerSerhiy Storchaka <storchaka@gmail.com>
Sat, 18 Jul 2015 20:20:50 +0000 (23:20 +0300)
1  2 
Lib/inspect.py
Lib/test/test_inspect.py
Misc/NEWS

diff --cc Lib/inspect.py
index 45679cfc0b5e3a1bfa789d4b62d60941dd8a9d1b,4298de6b60c267ca386d934a8867ed3802de3f99..42f24cddba8745f09245d4331f1328140a62562d
@@@ -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:
  
          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.
  
          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()
  
index a02f2e1b6069f2e91d8301495be3b9f5e3c97b70,1a124b5db2360ad44812d5be63f5921740668390..8e0e73cf007d5371b5c837a214d10b95b83feed2
@@@ -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)
  
-         self.assertEqual(ba1, ba2)
 +        def foo(*, a, b): pass
 +        sig = inspect.signature(foo)
 +        ba1 = sig.bind(a=1, b=2)
 +        ba2 = sig.bind(b=2, a=1)
++        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'<BoundArguments \(a=20,.*\}\}\)>')
 +
 +    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 ba01a06ac6163574b8f61b68169d4301c4421a94,ad5fef33fadbf97e03daea139a93b0782260951d..b400a9121143634a3266e2d55dbf8f2161ac7b14
+++ 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.