You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ENH: np.vectorize hides signature during jupyter introspection. #29824
If I write a function in a python module or in my juptyer notebook, when I am writing to call that function in a jupyter notebook, I can press tab to see the function signature and docstring. This helps me quickly write code, especially if the function has many arguments. However if I have wrapped the function in np.vectorize I instead see documentation for the vectorize class. This spoils the benefits of the tab complete tooling available in jupyter and other IDE-like environments.
I would like if I could still see the docstring for my function even when it is wrapped in np.vectorize. I know there are tools like functools.wraps that can help with this sort of thing, but I also know that it can still be tricky to get the desired behavior. I wonder if this has been considered before for np.vectorize?
Hmmmm, interesting, np.vectorize does set __doc__ similar to np.mean (implementation detail, this gets also wrapped) and using functools.wraps doesn't seem sufficient to me?
I must be missing something that I probably knew at some point. IPython does do this nicely for np.mean which also is a class (although defined in C?) but doens't show the class docs, but rather the __doc__ that was set (or maybe via __wrapped__).
Curious, did you have a working invocation to fix it?
@seberg thank you for your quick response and looking into it. No I don't currently have an invocation for this. I was curious to know the prior art within numpy before diving into figuring out what is going on. I see the same issue in Pycharm.
Ok, I did a little more testing. So the docstring set for the function DOES appropriately show up. Originally my function had no docstring.
However. what I really want to see is the function signature.
Here is an example without np.vectorize where I can helpfully see the function signature
Here is the same example with np.vectorize where I can't see the function signature. However, I can see the docstring.
Here is np.mean where I can see the signature.
So I would say I gave this issue the wrong title. Maybe I should say 'np.vectorize hides function signature during jupyter introspection`. Let me know if I should change it.
changed the title [-]ENH: `np.vectorize` hides docstrings during jupyter introspection.[/-][+]ENH: `np.vectorize` hides signature during jupyter introspection.[/+]on Sep 30, 2025
Ah, it's all there. Your doc string is just so short that it is hard to see...
Anyway, I think this actually can be improved (also for _ArrayFunctionDispatcher, although maybe that would mean removing the docs completely).
I think we can move the class docstring into the __init__. I think that'll render just as well, but then it shouldn't show up as Class Docstring and spam the output here.
Ah, it's all there. Your doc string is just so short that it is hard to see...
Anyway, I think this actually can be improved (also for _ArrayFunctionDispatcher, although maybe that would mean removing the docs completely). I think we can move the class docstring into the __init__. I think that'll render just as well, but then it shouldn't show up as Class Docstring and spam the output here.
@seberg sorry, see my last post more carefully. It is not "all" there. The docstring is there, but the function signature is not there. The function signature is really what I'm working. A workaround is that I can put documentation about the function parameters inside the docstring which is obviously a good practice. However I'm wanting this for functions where I'm just doing fast turnaround work in a jupyter notebook and there's no need to bother with writing docstrings. Also there can be a desync between the real function signature and what is in the docstring. So what I really want is for the Signature: row in the tooltip to match the actual function signature like it does when I'm not using np.vectorize. See my examples above.
But, separately, what you describe about getting the np.vectorize class docstring out of the tooltip would be a nice improvement.
@jagerber48 ah, yes... We should still copy over those other attributes (or use functools.update_wrapper although I have heard of opinions that it is a bit heavy weight). I got confused things about things due to missing the docs (them being short).
I think both are valid improvements, I am not going to jump on to it, but if you or someone else wants to make a PR we should explore both.
Hi, I have read the discussion and this looks like a great issue to start with. If @jagerber48 does not minds, I’d love to take this on as my first contribution to NumPy.
Hi, I have read the discussion and this looks like a great issue to start with. If @jagerber48 does not minds, I’d love to take this on as my first contribution to NumPy.
@Abhi210 That sounds great to me! I would be happy to help review the PR.
Thank you, @jagerber48, for giving me this opportunity. This is my first contribution to any open-source project. So, here is what I did:
Your issue (as I understood): When you decorated the Python function with np.vectorize and attempted to view the type hints for the function, you could not see the function signature.
What I did: Since we wanted to see the signature of the decorated function, I used inspect.signature() on that function to retrieve its signature. This allows the Jupyter IDE to get the signature of the function. Here are the images describing the before vs after setup:
The changes I did are in the file: numpy/lib/_function_base_impl.py on line 2492 (after self._doc=doc line):
Since we needed the inspect module, I had to import it at the start of this file.
@jagerber48: Does this address what you were aiming for? @seberg: Is this the right approach, or would you prefer a different way to implement what @jagerber48 wanted? I used ChatGPT for some help regarding this issue. Further,
I think we can move the class docstring into the init. I think that'll render just as well, but then it shouldn't appear as a Class Docstring and clutter the output here.
I did not understand this statement. What do we intend to do here?
Further, if you type np.vectorize, it will give the type hints for the np.vectorize function.
@Abhi210 interesting. Thank you for taking a look at this. What you've done does technically satisfy my requirement about getting the correct signature to appear in the tooltip under Signature. However, I don't like that the "Call Signature" field has been added in your example.
In my example for the non-wrapped version there were 4 fields in the tooltip. "Signature", "Docstring", "File, "Type". In my example the wrapped version had two additional fields, "String Form" and "Class Docstring".
Expanding the scope of this issue a little bit, I might change the requirement to be something like: I want the tooltip for the wrapped callable to be the exact same as the tooltip for the non-wrapped callable. It is probably possible to get that right for this simple function, but it might be more complicated when you take into account that the wrapped thing might be some more complicated class, or a lambda or something. I guess after reading through the responses in this issue it sounds like there hasn't been too much thought into getting this kind of thing right yet, so I think there's a chance with just a little more work like what you've done, we can get something that works nicely. However two important things:
@seberg, do you agree with the goal that the tooltip for the wrapped function should exactly match the tooltip for the non-wrapped function
If you come up with a more thorough approach, it will be important to test it against more cases than just the simple function I've provided. The two examples that spring to mind is that it should work for more complicated user defined classes that are callable, and it should work for lambda functions. It should work for functions defined in the notebook like this one, but also for imported functions. These are all I can think of offhand, but there are probably other important test cases.
I would need to audit whether it isn't lying (I don't think it is, though. Vectorize doesn't add ufunc parameters, I believe). __annotations__ would be dubious, though, since the types definitely change.
wrapped callable to be the exact same as the tooltip for the non-wrapped callable
Well, type and repr isn't and shouldn't be. It isn't the same thing. If this was designed purely as a decorator use, I might view it differently, but vectorize isn't.
Thank you, @jagerber48, for suggesting the test cases. I tested my changes over different types of function calls that you suggested: lambda functions, functions from another module, and callables inside the class. In all the cases, the tooltip shows the Signature of the function. However, the "Call Signature" is still present inside the tooltip. I am thinking of some ways to remove them. I am attaching an image showing the results of the change on a lambda function:
Also, should we really want the tooltip for a decorated function to match a non-decorated function? For example, users may also need to refer to the documentation for np.vectorize to add more options to the decorator. So, in this sense, the additional fields in the tooltip might come in handy.
@Abhi210 I probably need to look at the code to understand what it is exactly I want. Basically I understand now that the wrapped object is an instance of the np.vectorize class with the __call__ function set to call the wrapped function foo() in a vectorized way. So I think I want all the documentation about my foo() function to appear in place of any documentation for np.vectorize.__call__(). After this recent discussion, I think that means I want the signature of foo() to appear as the Call Signature for the wrapped version of foo().
I think seberg's suggestion is to move the documentation that currently appears as the class docstring (appearing right under class vectorize: ... to be the documentation under vectorize.__init__(). This way when someone is invoking vectorize they will be able to see the documentation, but that documentation won't appear on the wrapped functions that are instances of the vectorize class. This will get rid of the long Class Docstring of the tooltip that (1) is irrelevant and (2) pollutes the tooltip, making it hard to find the actual information you care about your wrapped function.
I'm looking at the vectorize source code a bit. Here are some things I'm thinking:
I agree with @seberg that we could try moving the docstring nested under the vectorize class definition to be under vectorize.__init__.
I see that self.__doc__ is set to pyfunc.__doc__. I wonder if we actually want to set self.__call__.__doc__ to pyfunc.__doc__.
In your change so far you set self.__signature__ to inspect.signature(pyfunc). Again, maybe we want to actually be setting self.__call__.__signature to inspect.signature(pyfunc).
I think this reflects the semantics we want and I think there's a good chance this will make the tooltip look the way we want.
Ok, I see that you can't set self.__call__.__signature__, the proper thing to do is set self.__signature__ as you have already done. However, check out this example in a jupyter notebook.
This tooltip is pretty good. I think it is more accurately reflecting what we want. However, I do consider it a wart that Call Signature is appearing with pretty much useless information. I also wonder if we're breaking the typical semantics by injecting the signature into Signature instead of Call Signature. Like I wonder if Signature typically reflects Init Signature whereas Call Signature is what is meant to reflect the signature of __call__().
I've done some research on Call Signature vs Signature and I've done some testing in jupyter. It seems like ipython checks inspect.signature(foo.__call__) and inspect.signature(foo). If these are different it will display the former as Call Signature and the latter as Signature. @Abhi210 discovered we can spoof inspect.signature(foo) by assigning foo.__signature__. However, it is not possible to overwrite foo.__call__.__signature__ because foo.__call__ is a bound method.
In some time I haven't been able to figure out a way to work around this issue.
I would be happy to have "Signature" reflect what I am looking for and to have "Call Signature" just be superfluous and useful even though it's annoying.
I tried using functools.update_wrapper to set attributes like __doc__, __name__, and __wrapped__ for the wrapped function. However, the "Call Signature" field still appears in the tooltip.
As @jagerber48 noted, since foo.__call__ is a bound method, its __signature__ cannot be modified directly. Therefore, it appears that our current approach of spoofing inspect.signature(foo) by assigning it to foo.__signature_ is the most viable solution. The persistent "Call Signature" field appears to be an IPython-specific behavior rather than a NumPy issue.
Following @seberg’s suggestion, I’ve also moved the vectorize class docstring inside the __init__ method to avoid it appearing as a “Class Docstring” in the tooltip.
@seberg, @jagerber48 — could you please review and let me know if you think this is ready for a commit?
Hi @jagerber48! Just checking in to see if you had a chance to review my last comment.
I’ve implemented the suggested changes (moved the docstring into __init__, added __signature__ and __wrapped__ handling, and tested across multiple cases). If everything looks good to you, I’d be happy to open a PR for this.
@Abhi210 can you please make a PR so that we can discuss the code changes in detail? It would be helpful to see the code diff together with more screen captures from the jupyter tooltip.
Activity
seberg commentedon Sep 29, 2025
Hmmmm, interesting,
np.vectorizedoes set__doc__similar tonp.mean(implementation detail, this gets also wrapped) and usingfunctools.wrapsdoesn't seem sufficient to me?I must be missing something that I probably knew at some point. IPython does do this nicely for
np.meanwhich also is a class (although defined in C?) but doens't show the class docs, but rather the__doc__that was set (or maybe via__wrapped__).Curious, did you have a working invocation to fix it?
jagerber48 commentedon Sep 29, 2025
@seberg thank you for your quick response and looking into it. No I don't currently have an invocation for this. I was curious to know the prior art within
numpybefore diving into figuring out what is going on. I see the same issue in Pycharm.Ok, I did a little more testing. So the docstring set for the function DOES appropriately show up. Originally my function had no docstring.
However. what I really want to see is the function signature.
Here is an example without
np.vectorizewhere I can helpfully see the function signatureHere is the same example with
np.vectorizewhere I can't see the function signature. However, I can see the docstring.Here is
np.meanwhere I can see the signature.So I would say I gave this issue the wrong title. Maybe I should say '
np.vectorizehides function signature during jupyter introspection`. Let me know if I should change it.[-]ENH: `np.vectorize` hides docstrings during jupyter introspection.[/-][+]ENH: `np.vectorize` hides signature during jupyter introspection.[/+]seberg commentedon Oct 1, 2025
Ah, it's all there. Your doc string is just so short that it is hard to see...
Anyway, I think this actually can be improved (also for
_ArrayFunctionDispatcher, although maybe that would mean removing the docs completely).I think we can move the class docstring into the
__init__. I think that'll render just as well, but then it shouldn't show up asClass Docstringand spam the output here.jagerber48 commentedon Oct 1, 2025
@seberg sorry, see my last post more carefully. It is not "all" there. The docstring is there, but the function signature is not there. The function signature is really what I'm working. A workaround is that I can put documentation about the function parameters inside the docstring which is obviously a good practice. However I'm wanting this for functions where I'm just doing fast turnaround work in a jupyter notebook and there's no need to bother with writing docstrings. Also there can be a desync between the real function signature and what is in the docstring. So what I really want is for the
Signature:row in the tooltip to match the actual function signature like it does when I'm not usingnp.vectorize. See my examples above.But, separately, what you describe about getting the
np.vectorizeclass docstring out of the tooltip would be a nice improvement.seberg commentedon Oct 1, 2025
@jagerber48 ah, yes... We should still copy over those other attributes (or use
functools.update_wrapperalthough I have heard of opinions that it is a bit heavy weight). I got confused things about things due to missing the docs (them being short).I think both are valid improvements, I am not going to jump on to it, but if you or someone else wants to make a PR we should explore both.
Abhi210 commentedon Oct 2, 2025
Hi, I have read the discussion and this looks like a great issue to start with. If @jagerber48 does not minds, I’d love to take this on as my first contribution to NumPy.
jagerber48 commentedon Oct 5, 2025
@Abhi210 That sounds great to me! I would be happy to help review the PR.
Abhi210 commentedon Oct 6, 2025
Thank you, @jagerber48, for giving me this opportunity. This is my first contribution to any open-source project. So, here is what I did:
Your issue (as I understood): When you decorated the Python function with np.vectorize and attempted to view the type hints for the function, you could not see the function signature.
What I did: Since we wanted to see the signature of the decorated function, I used inspect.signature() on that function to retrieve its signature. This allows the Jupyter IDE to get the signature of the function. Here are the images describing the before vs after setup:
The changes I did are in the file: numpy/lib/_function_base_impl.py on line 2492 (after self._doc=doc line):
Since we needed the inspect module, I had to import it at the start of this file.
@jagerber48: Does this address what you were aiming for?
@seberg: Is this the right approach, or would you prefer a different way to implement what @jagerber48 wanted? I used ChatGPT for some help regarding this issue. Further,
I did not understand this statement. What do we intend to do here?
Further, if you type np.vectorize, it will give the type hints for the np.vectorize function.
jagerber48 commentedon Oct 6, 2025
@Abhi210 interesting. Thank you for taking a look at this. What you've done does technically satisfy my requirement about getting the correct signature to appear in the tooltip under
Signature. However, I don't like that the "Call Signature" field has been added in your example.In my example for the non-wrapped version there were 4 fields in the tooltip. "Signature", "Docstring", "File, "Type". In my example the wrapped version had two additional fields, "String Form" and "Class Docstring".
Expanding the scope of this issue a little bit, I might change the requirement to be something like: I want the tooltip for the wrapped callable to be the exact same as the tooltip for the non-wrapped callable. It is probably possible to get that right for this simple function, but it might be more complicated when you take into account that the wrapped thing might be some more complicated class, or a lambda or something. I guess after reading through the responses in this issue it sounds like there hasn't been too much thought into getting this kind of thing right yet, so I think there's a chance with just a little more work like what you've done, we can get something that works nicely. However two important things:
seberg commentedon Oct 6, 2025
I would need to audit whether it isn't lying (I don't think it is, though. Vectorize doesn't add ufunc parameters, I believe).
__annotations__would be dubious, though, since the types definitely change.Well,
typeandreprisn't and shouldn't be. It isn't the same thing. If this was designed purely as a decorator use, I might view it differently, butvectorizeisn't.Abhi210 commentedon Oct 6, 2025
Thank you, @jagerber48, for suggesting the test cases. I tested my changes over different types of function calls that you suggested: lambda functions, functions from another module, and callables inside the class. In all the cases, the tooltip shows the Signature of the function. However, the "Call Signature" is still present inside the tooltip. I am thinking of some ways to remove them. I am attaching an image showing the results of the change on a lambda function:
Also, should we really want the tooltip for a decorated function to match a non-decorated function? For example, users may also need to refer to the documentation for np.vectorize to add more options to the decorator. So, in this sense, the additional fields in the tooltip might come in handy.
jagerber48 commentedon Oct 6, 2025
@Abhi210 I probably need to look at the code to understand what it is exactly I want. Basically I understand now that the wrapped object is an instance of the
np.vectorizeclass with the__call__function set to call the wrapped functionfoo()in a vectorized way. So I think I want all the documentation about myfoo()function to appear in place of any documentation fornp.vectorize.__call__(). After this recent discussion, I think that means I want the signature offoo()to appear as theCall Signaturefor the wrapped version offoo().I think seberg's suggestion is to move the documentation that currently appears as the class docstring (appearing right under
class vectorize: ...to be the documentation undervectorize.__init__(). This way when someone is invokingvectorizethey will be able to see the documentation, but that documentation won't appear on the wrapped functions that are instances of thevectorizeclass. This will get rid of the longClass Docstringof the tooltip that (1) is irrelevant and (2) pollutes the tooltip, making it hard to find the actual information you care about your wrapped function.I'm looking at the
vectorizesource code a bit. Here are some things I'm thinking:vectorizeclass definition to be undervectorize.__init__.self.__doc__is set topyfunc.__doc__. I wonder if we actually want to setself.__call__.__doc__topyfunc.__doc__.self.__signature__toinspect.signature(pyfunc). Again, maybe we want to actually be settingself.__call__.__signaturetoinspect.signature(pyfunc).I think this reflects the semantics we want and I think there's a good chance this will make the tooltip look the way we want.
jagerber48 commentedon Oct 6, 2025
Ok, I see that you can't set
self.__call__.__signature__, the proper thing to do is setself.__signature__as you have already done. However, check out this example in a jupyter notebook.This tooltip is pretty good. I think it is more accurately reflecting what we want. However, I do consider it a wart that
Call Signatureis appearing with pretty much useless information. I also wonder if we're breaking the typical semantics by injecting the signature intoSignatureinstead ofCall Signature. Like I wonder ifSignaturetypically reflectsInit SignaturewhereasCall Signatureis what is meant to reflect the signature of__call__().jagerber48 commentedon Oct 7, 2025
I've done some research on
Call SignaturevsSignatureand I've done some testing injupyter. It seems likeipythonchecksinspect.signature(foo.__call__)andinspect.signature(foo). If these are different it will display the former as Call Signature and the latter as Signature. @Abhi210 discovered we can spoofinspect.signature(foo)by assigningfoo.__signature__. However, it is not possible to overwritefoo.__call__.__signature__becausefoo.__call__is a bound method.In some time I haven't been able to figure out a way to work around this issue.
I would be happy to have "Signature" reflect what I am looking for and to have "Call Signature" just be superfluous and useful even though it's annoying.
Abhi210 commentedon Oct 7, 2025
I tried using
functools.update_wrapperto set attributes like__doc__,__name__,and__wrapped__for the wrapped function. However, the "Call Signature" field still appears in the tooltip.As @jagerber48 noted, since
foo.__call__is a bound method, its__signature__cannot be modified directly. Therefore, it appears that our current approach of spoofinginspect.signature(foo)by assigning it tofoo.__signature_is the most viable solution. The persistent "Call Signature" field appears to be an IPython-specific behavior rather than a NumPy issue.Following @seberg’s suggestion, I’ve also moved the vectorize class docstring inside the
__init__method to avoid it appearing as a “Class Docstring” in the tooltip.@seberg, @jagerber48 — could you please review and let me know if you think this is ready for a commit?
Abhi210 commentedon Oct 13, 2025
Hi @jagerber48! Just checking in to see if you had a chance to review my last comment.
I’ve implemented the suggested changes (moved the docstring into
__init__,added__signature__and__wrapped__handling, and tested across multiple cases). If everything looks good to you, I’d be happy to open a PR for this.Thank you!
jagerber48 commentedon Oct 13, 2025
@Abhi210 can you please make a PR so that we can discuss the code changes in detail? It would be helpful to see the code diff together with more screen captures from the jupyter tooltip.
ENH: fix call signature display for np.vectorize during jupyter
ENH: fix call signature display for np.vectorize during jupyter
Merge branch 'main' into numpygh-29824-vectorize-signature-fix
Merge branch 'main' into numpygh-29824-vectorize-signature-fix
Merge branch 'main' into numpygh-29824-vectorize-signature-fix
Merge branch 'upstream/main' into numpygh-29824-vectorize-signature-fix