8000 MAINT: Remove internal uses of _inspect module by rossbar · Pull Request #16787 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

MAINT: Remove internal uses of _inspect module #16787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions numpy/core/overrides.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import functools
import os
import textwrap
import inspect

from numpy.core._multiarray_umath import (
add_docstring, implement_array_function, _get_implementing_args)
from numpy.compat._inspect import getargspec


ARRAY_FUNCTION_ENABLED = bool(
Expand Down Expand Up @@ -66,29 +66,36 @@
""")


ArgSpec = collections.namedtuple('ArgSpec', 'args varargs keywords defaults')
# Avoid extra getattr calls in verify_matching_signatures
_Parameter_empty = inspect.Parameter.empty


def verify_matching_signatures(implementation, dispatcher):
"""Verify that a dispatcher function has the right signature."""
implementation_spec = ArgSpec(*getargspec(implementation))
dispatcher_spec = ArgSpec(*getargspec(dispatcher))

if (implementation_spec.args != dispatcher_spec.args or
implementation_spec.varargs != dispatcher_spec.varargs or
implementation_spec.keywords != dispatcher_spec.keywords or
(bool(implementation_spec.defaults) !=
bool(dispatcher_spec.defaults)) or
(implementation_spec.defaults is not None and
len(implementation_spec.defaults) !=
len(dispatcher_spec.defaults))):
raise RuntimeError('implementation and dispatcher for %s have '
'different function signatures' % implementation)

if implementation_spec.defaults is not None:
if dispatcher_spec.defaults != (None,) * len(dispatcher_spec.defaults):
raise RuntimeError('dispatcher functions can only use None for '
'default argument values')
implementation_sig = inspect.signature(implementation)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can only assume the timings are bad because inspect.signature is slower than getargspec

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, some (very rough) profiling indicates that this is the case. Using the following setup:

from numpy.core.overrides import verify_matching_signatures                     
                                                                                
def foo(                                                                        
    a, b, m, n, k, copy=False, color="red", normed=False, weights=None,         
    bins=10, density=None, size=1024, shape=(3, 4)                              
):                                                                              
    pass                                                                        
                                                                                
def _foo_dispatcher(                                                            
    a, b, m, n, k, copy=None, color=None, normed=None, weights=None,            
    bins=None, density=None, size=None, shape=None                              
):                                                                              
    pass

timeit gives:

On 6e35c2a (this PR):

In [2]: %timeit verify_matching_signatures(foo, _foo_dispatcher)                             
73.1 µs ± 879 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

On 2283e26 (root of branch):

In [2]: %timeit verify_matching_signatures(foo, _foo_dispatcher)                             
4.85 µs ± 11.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

So the implementation based on inspect.signature is about 15x slower than the original based on _inspect.getargspec.

One idea I had was to try to re-implement this with inspect.getfullargspec, which is the more full-featured extension of the original (now deprecated) inspect.getargspec. I will update with -X importtime & profiling info from the attempt.

Copy link
Member
@eric-wieser eric-wieser Aug 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea I had was to try to re-implement this with inspect.getfullargspec, which is the more full-featured extension of the original (now deprecated) inspect.getargspec.

This is implemented in terms of inspect.signature, then does some extra processing. You'd do better to probe the __code__ object directly.

Perhaps the comparison is the slow part.

dispatcher_sig = inspect.signature(dispatcher)

implementation_sig_with_none_defaults = inspect.Signature(
parameters=[
p.replace(
annotation=_Parameter_empty,
default=(
_Parameter_empty
if p.default is _Parameter_empty
else None
),
)
for p in implementation_sig.parameters.values()
],
)

if dispatcher_sig != implementation_sig_with_none_defaults:
raise RuntimeError(
f"dispatcher for {implementation} has the wrong function "
"signature:\n"
" expected: {implementation_sig_with_none_defaults}\n"
" got: {dispatcher_sig}"
)


def set_module(module):
Expand Down
8 changes: 3 additions & 5 deletions numpy/ma/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@
from numpy import ndarray, amax, amin, iscomplexobj, bool_, _NoValue
from numpy import array as narray
from numpy.lib.function_base import angle
from numpy.compat import (
getargspec, formatargspec, long, unicode, bytes
)
from numpy.compat import long, unicode, bytes
from numpy import expand_dims
from numpy.core.numeric import normalize_axis_tuple
from numpy.core._internal import recursive
Expand Down Expand Up @@ -135,8 +133,8 @@ def get_object_signature(obj):

"""
try:
sig = formatargspec(*getargspec(obj))
except TypeError:
sig = str(inspect.signature(obj))
except ValueError:
sig = ''
return sig

Expand Down
0