Numpy: BUG: numpy.core.overrides.verify_matching_signatures ignores keyword-only arguments

Created on 19 May 2020  路  1Comment  路  Source: numpy/numpy

Reproducing code example:

>>> from numpy.core.overrides import verify_matching_signatures
>>> def a(*, foo=1): pass
>>> def b(*, bar=2): pass
>>> verify_matching_signatures(a, b)
# does not fail

A correct implementation could look like:

def verify_matching_signatures(implementation, dispatcher):
    import inspect
    implementation_sig = inspect.signature(implementation)
    dispatcher_sig = inspect.signature(dispatcher)

    # replace defaults with None and remove annotations
    implementation_with_none_sig = implementation_sig.replace(
        parameters=[
            p.replace(
                annotation=inspect.Parameter.empty,
                default=inspect.Parameter.empty if p.default is inspect.Parameter.empty else None
            )
            for p in implementation_sig.parameters.values()
        ],
        return_annotation=inspect.Signature.empty
    )

    if dispatcher_sig != implementation_with_none_sig:
        raise RuntimeError(
            'dispatcher for {} has the wrong function signature:\n'
            '  expected: {}\n'
            '  got:      {}'
            .format(
                implementation, implementation_with_none_sig, dispatcher_sig
            )
        )

which gives:

>>> verify_matching_signatures(a, b)
RuntimeError: dispatcher for <function a at 0x0000020080E14B80> has the wrong function signature:
  expected: (*, foo=None)
  got:      (*, bar=2)

Unfortunately, this implementation seems to slow down the import time by 5%.

00 - Bug

Most helpful comment

Numpy inspection doesn't handle kwonly arguments. It can either cause the arguments not to output from the inspection, or even mess up to order of * and ** arguments, since the kwonly args aren't counted (messing up the index of the names). Example:

from numpy.compat._inspect import getargspec


def checkargs(a):
    print(getargspec(a))


checkargs(lambda a, foo=1: 0)
# Output:   (['a', 'foo'], None, None, (1,))
# Correct
checkargs(lambda a, *, foo=1: 0)
# Output:   (['a'], None, None, None)
# Expected: (['a', 'foo'], None, None, (1,))
checkargs(lambda *args, **kwargs: 0)
# Output:   ([], 'args', 'kwargs', None)
# Correct
checkargs(lambda a, *args, foo=1, **kwargs: 0)
# Output:   (['a'], 'foo', 'args', None)
# Expected: (['a', 'foo'], 'args', 'kwargs', (1,)

I am fixing the issue in inspection, having it count the code.co_kwonlyargcount and read function.__kwdefaults__, along with adding your test-case for numpy.core.overrides.verify_matching_signatures. It seems more appropriate than bypassing the bug in the inspection functions, but it's possible that it has side effects if something is expecting this (wrong) behaviour somewhere else.

>All comments

Numpy inspection doesn't handle kwonly arguments. It can either cause the arguments not to output from the inspection, or even mess up to order of * and ** arguments, since the kwonly args aren't counted (messing up the index of the names). Example:

from numpy.compat._inspect import getargspec


def checkargs(a):
    print(getargspec(a))


checkargs(lambda a, foo=1: 0)
# Output:   (['a', 'foo'], None, None, (1,))
# Correct
checkargs(lambda a, *, foo=1: 0)
# Output:   (['a'], None, None, None)
# Expected: (['a', 'foo'], None, None, (1,))
checkargs(lambda *args, **kwargs: 0)
# Output:   ([], 'args', 'kwargs', None)
# Correct
checkargs(lambda a, *args, foo=1, **kwargs: 0)
# Output:   (['a'], 'foo', 'args', None)
# Expected: (['a', 'foo'], 'args', 'kwargs', (1,)

I am fixing the issue in inspection, having it count the code.co_kwonlyargcount and read function.__kwdefaults__, along with adding your test-case for numpy.core.overrides.verify_matching_signatures. It seems more appropriate than bypassing the bug in the inspection functions, but it's possible that it has side effects if something is expecting this (wrong) behaviour somewhere else.

Was this page helpful?
0 / 5 - 0 ratings