classification
Title: Remove explicit type check from inspect.Signature.from_function()
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.4
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: yselivanov Nosy List: benjamin.peterson, eric.araujo, larry, ncoghlan, pitrou, python-dev, scoder, terry.reedy, yselivanov
Priority: normal Keywords: needs review, patch

Created on 2013-02-08 15:20 by scoder, last changed 2014-03-17 06:31 by python-dev. This issue is now closed.

Files
File name Uploaded Description Edit
inspect_sig.patch scoder, 2013-02-08 15:50 proposed patch review
inspect_sig_with_docstring.patch scoder, 2013-02-08 16:02 same patch plus updated docstring review
inspect_sig_2.patch scoder, 2013-02-08 21:44 new patch that keeps the original TypeError if an attribute is not found review
inspect_sig_3.patch scoder, 2013-02-17 05:59 updated patch that also fixes the lower case "python" in the docstring review
sig_func_ducktype_01.patch yselivanov, 2014-01-30 16:49 review
sig_func_ducktype_02.patch yselivanov, 2014-01-30 17:28 review
sig_func_ducktype_03.patch yselivanov, 2014-01-31 19:19 review
divert_from_builtin.patch scoder, 2014-02-01 19:51 divert Signature.from_builtin() to .from_function() for complete function interfaces review
sig_cython_01.patch yselivanov, 2014-02-01 21:48 review
sig_cython_02.patch yselivanov, 2014-02-01 22:09 Tweaked unittest a bit review
sig_cython_03.patch yselivanov, 2014-02-02 02:32 A bit more testing, better coverage review
sig_cython_latest_01.patch yselivanov, 2014-02-13 22:46 review
Messages (67)
msg181674 - (view) Author: Stefan Behnel (scoder) * Date: 2013-02-08 15:20
I can't see a reason why Signature.from_function() should explicitly check the type of the object being passed in. As long as the object has all required attributes, it should be accepted.

This is specifically an issue with Cython compiled functions, which are not Python functions but look the same.
msg181676 - (view) Author: Stefan Behnel (scoder) * Date: 2013-02-08 15:50
This patch removes the type check from Signature.from_function() and cleans up the type tests in signature() to use whatever the inspect module defines as isfunction() and isbuiltin(), so that it becomes properly monkey-patchable.

It also adds a test that makes sure the signature relies only on the four relevant special attributes, not on the type of the object being inspected.
msg181677 - (view) Author: Éric Araujo (eric.araujo) * (Python committer) Date: 2013-02-08 15:55
Patch looks good, but I’m worried about the change from TypeError to AttributeError in a stable version.

Could you also make clear that all function-like objects are accepted in the doc?
msg181678 - (view) Author: Stefan Behnel (scoder) * Date: 2013-02-08 16:02
The method doesn't seem to be documented, and I'm not sure if the docstring really benefits from this lengthy addition. Anyway, here's a patch that includes the docstring update.

The exception could be kept the same if we catch an AttributeError and explicitly raise a TypeError instead.
msg181702 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2013-02-08 21:20
My first concern is whether this is a behavior issue that can be fixed in the next 3.3 release or an enhancement request that should wait for 3.4.

If Signature.from_function were documented in the manual with the current limitation, this issue would definitely be an enhancement request. But it is not even mentioned in the manual. I presume this is intentional as it is pretty clearly not meant to be called directly but only from signature(). Perhaps its should have been given a leading underscore to mark it as private.

The docstring does say 'python function'. However, its type check is redundant with the guard in signature before its call. So in normal usage, it can never fail. Moreover, the limitation is completely unnecessary even for pure Python code, as shown the the ease of creating a funclike class for the test. It is contrary to Python policy and stdlib practice. So in a sense, it is a bug.

My conclusion: the proposed change is an enhancement bugfix (or bugfix enhancement?) for an undocumented private function. So I think it ok for 3.3, but would not blame anyone who disagreed.

---
I agree with Éric that the exception should not be changed, not just because it would be a change, but because is would be a wrong change. Signature.from_function(42) *is* a type error. The fact that the type error is detected by attribute rather than by class is not relevant to external callers. So please wrap the
   # Parameter information.
section of the code with try: except AttributeError, as a substitute for the current too-narrow type check. Leave the message as it, except possibly add the object that fails:
 "{} is not a function".format(func)

---
The change to signature has no effect on .from_function but makes it consistent with the rest of the module.
msg181704 - (view) Author: Stefan Behnel (scoder) * Date: 2013-02-08 21:44
Fine with me. Updated patch attached.
msg181805 - (view) Author: Stefan Behnel (scoder) * Date: 2013-02-10 13:53
Any more comments? Any objections to applying the last patch? Anyone ready to do it?
msg181806 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-02-10 13:56
I certainly think the patch is ok on the principle. I'll let someone else pronounce on the details :-)
msg181868 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2013-02-11 01:46
I am puzzled by this part of the patch:

- with self.assertRaisesRegex(TypeError, 'is not a callable object'):
+ with self.assertRaisesRegex(TypeError, '42.*is not a callable object'):

With 3.3, I get "TypeError: 42 is not a callable object", which I consider correct, and which should not be changed by the two changes to signature().  So both the old test, missing '42' (how does it pass?) and the new version, with extraneous '.*' look wrong.

I am also puzzled by the 'from None' part in
+ raise TypeError("'{!r}' is not a Python function".format(func)) from None

While I remember that being in the pydev discussion and while "raise XyzError from None" executes, it does not seems to be documented for 3.3 in 7.8. The raise statement. (Should this be another issue?) In fact, that section says " if given, the second expression must be another exception class or instance", which excludes None.
msg181879 - (view) Author: Stefan Behnel (scoder) * Date: 2013-02-11 07:44
1) that's a regex test, so it just looks for the text. I only extended it to include the object it's supposed to test for. It's not actually related to my changes, but I considered it the right thing to do. I used "42.*is not" in order to allow for alternative spellings like "'42' is not...".

2) "raise ... from None" is the official way to explicitly drop the exception context from a newly raised exception. Otherwise, you'd get two exceptions in this case: a TypeError (as main exception) and an AttributeError (as its context), which I do not consider helpful here as it bloats the output for an otherwise simple error. For 99% of the use cases, it won't matter which attribute was missing.
msg181887 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-02-11 09:46
On Mon, Feb 11, 2013 at 11:46 AM, Terry J. Reedy <report@bugs.python.org> wrote:
> I am also puzzled by the 'from None' part in
> + raise TypeError("'{!r}' is not a Python function".format(func)) from None
>
> While I remember that being in the pydev discussion and while "raise XyzError from None" executes, it does not seems to be documented for 3.3 in 7.8. The raise statement. (Should this be another issue?) In fact, that section says " if given, the second expression must be another exception class or instance", which excludes None.

That's a separate docs bug - it seems we missed the necessary language
reference updates when implementing PEP 309. The relevant docs are
here: http://docs.python.org/3/library/exceptions.html#built-in-exceptions
msg181940 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2013-02-12 01:21
I opened separate issue #17188:
Document 'from None' in raise statement doc.
msg182262 - (view) Author: Stefan Behnel (scoder) * Date: 2013-02-17 05:59
Updated patch to also fix a little typo in the docstring (lower case "python").
msg209675 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-01-29 20:44
Stefan, is this one still relevant?

If it is, I can review it (I have a couple of things to improve) and push in 3.4, as it doesn't really change any API.
msg209708 - (view) Author: Stefan Behnel (scoder) * Date: 2014-01-30 07:15
Yes, the situation didn't change.
msg209726 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-01-30 16:49
Stefan,

I'm attaching a reviewed patch--was a bit easier to rebase it on the new code and fix the remaining issues.

Please review.

BTW, do cython functions have __name__ attribute?
msg209728 - (view) Author: Stefan Behnel (scoder) * Date: 2014-01-30 17:15
LGTM, thanks!

And works nicely with Cython:

"""
import inspect

def test_isfunction():
    """
    >>> test_isfunction()
    True
    """
    return inspect.isfunction(test_isfunction)

def test_signature():
    """
    >>> test_signature()   # doctest: +ELLIPSIS
    <inspect.Signature object ...>
    """
    return inspect.signature(test_signature)
"""

> BTW, do cython functions have __name__ attribute?

Yes, they have everything that can possibly be emulated (although not
everything currently contains useful information). If your question is
whether that should be tested for, too, then I'd say yes.
msg209729 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-01-30 17:28
Attached is a second patch (sig_func_ducktype_02.patch), with a bit improved tests (and a small bug fixed).

Larry, do you think it's OK if I merge this one in 3.4? Technically, it's a feature that we start to support Cython functions in inspect.signature, but from the standpoint of the API everything is the same.
msg209775 - (view) Author: Stefan Behnel (scoder) * Date: 2014-01-31 12:07
pretty please? :)
msg209787 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-01-31 13:22
Tell you what.  If this *specifically* is a fix for Cython, please add a some text saying that to the comment for the duck-type test function.

If you do that, and assuming that test_inspect still passes, then you may check it in.
msg209812 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-01-31 19:18
Third version of the patch is attached (sig_func_ducktype_03.patch).

Changes:

- Toughen the tests for duck-typed function -- do not accept classes
- Added comments to clarify the need of duck-typing
- NEWS item
- what's new item

Stefan, please take a look. I want to make sure, that the fact that we won't accept classes that look like a function, is OK with you.
msg209816 - (view) Author: Stefan Behnel (scoder) * Date: 2014-01-31 19:33
Tried it, works for me.

Explicitly rejecting classes is a good idea, IMHO, as is requiring that any function-like object must be callable, obviously.
msg209820 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-01-31 19:47
> Explicitly rejecting classes is a good idea, IMHO, as is requiring that any function-like object must be callable, obviously.

Yeah, I think it's good to restrict this duck-typing as much as possible. Committing the patch.
msg209821 - (view) Author: Roundup Robot (python-dev) Date: 2014-01-31 19:48
New changeset 32a660a52aae by Yury Selivanov in branch 'default':
inspect.signature: Support duck-types of Python functions (Cython, for instance) #17159
http://hg.python.org/cpython/rev/32a660a52aae
msg209822 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-01-31 19:50
OK, closing this one.
Stefan, Larry, thank you for your reviews and time.
msg209824 - (view) Author: Stefan Behnel (scoder) * Date: 2014-01-31 19:54
Thanks a lot!
msg209827 - (view) Author: Roundup Robot (python-dev) Date: 2014-01-31 20:22
New changeset 8a91132ed6aa by Yury Selivanov in branch 'default':
inspect.Signauture.from_function: validate duck functions in Signature constructor #17159
http://hg.python.org/cpython/rev/8a91132ed6aa
msg209896 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-01 16:20
Hmm, I now notice that I was mistaken about this working:

'''
import inspect

def test_isfunction():
    """
    >>> test_isfunction()
    True
    """
    return inspect.isfunction(test_isfunction)
'''

It only worked in Cython's test suite because its test runner monkey patches "inspect.isfunction", and I had completely forgotten about it. Sorry for the confusion.

The thing is that Cython's function type isn't really a Python function (obviously), it inherits from PyCFunction, so it should return True for isbuiltin(). A problem on our side prevented that. If I fix it up, then the newly added duck-typing code actually ends up not being used, because signature() tests for isbuiltin() first and runs into Signature.from_builtin(), which is the Argument Clinic code path that expects a textual signature representation. Cython functions don't have that, because they are compatible with Python functions.

This situation could be helped in inspect.signature() by reversing the test order, i.e. by changing this code

    if _signature_is_builtin(obj):
        return Signature.from_builtin(obj)

    if isfunction(obj) or _signature_is_functionlike(obj):
        # If it's a pure Python function, or an object that is duck type
        # of a Python function (Cython functions, for instance), then:
        return Signature.from_function(obj)

into this:

    if isfunction(obj) or _signature_is_functionlike(obj):
        # If it's a pure Python function, or an object that is duck type
        # of a Python function (Cython functions, for instance), then:
        return Signature.from_function(obj)

    if _signature_is_builtin(obj):
        return Signature.from_builtin(obj)

Would this be ok?

I would also argue that the implementation of _signature_is_builtin() is, well, not ideal, because what it should test for according to the comment at the top of the function is the existance of "__text_signature__". Instead, it does several type tests, one of which goes wrong in this case.
msg209907 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-01 18:39
> Would this be ok?

Probably. I need to take a closer look.

I'm not sure I like the idea that Cython functions are "chimeras" of some sort, i.e. they have a type of python builtin functions, hence, logically, Signature.from_builtin should work on them (and they have to follow __text_signature__ API), and on the other hand, they try to mimic pure python functions (being a builtin type) with all its guts like '__code__' object etc.  

Perhaps, what we need to do, is to modify 'Signature.from_builtin' to check for pure-python function duck type too, and fallback to 'Signature.from_function' in this case. Larry, Nick, what do you think?

> I would also argue that the implementation of _signature_is_builtin() is, well, not ideal, because what it should test for according to the comment at the top of the function is the existance of "__text_signature__". Instead, it does several type tests, one of which goes wrong in this case.

'from_builtin' needs to have those type checks. Duck typing is good, but some minimal type safety is good too.
msg209910 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-01 19:07
> I'm not sure I like the idea that Cython functions are "chimeras" of
> some sort, i.e. they have a type of python builtin functions, hence,
> logically, Signature.from_builtin should work on them (and they have to
> follow __text_signature__ API), and on the other hand, they try to mimic
> pure python functions (being a builtin type) with all its guts like
> '__code__' object etc.

That's one way of looking at it. The way I see it is that CPython's builtin
functions should rather behave exactly like Python functions. The fact that
there is such a thing as a "__text_signature__" and general special casing
of builtins is IMHO a rather annoying but truly long standing bug. The only
necessary difference is that one of them contains byte code and the other
doesn't, everything else should eventually be aligned.

> Perhaps, what we need to do, is to modify 'Signature.from_builtin' to
> check for pure-python function duck type too, and fallback to
> 'Signature.from_function' in this case.

In any case, I think that a complete Python function(-like) interface
should always be preferred to work-arounds like "__text_signature__",
regardless of where it comes from.

> 'from_builtin' needs to have those type checks. Duck typing is good, but
> some minimal type safety is good too.

I don't really see why. The code doesn't seem to be doing that much more
than text processing of the "__text_signature__", plus a tiny bit of
optional(!) attribute checking ("__module__" and "__self__").

The restrictive type checks appear to be the only thing that prevents users
from doing this:

    class myfunc:
        __text_signature__ = '(a,b,c,d)'

    sig = Signature.from_builtin(myfunc())

Granted, the name of that method doesn't really fit well in that case, and
a simpler interface than having to define a class would also not hurt.
msg209913 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-01 19:21
> That's one way of looking at it. The way I see it is that CPython's builtin
> functions should rather behave exactly like Python functions. The fact that
> there is such a thing as a "__text_signature__" and general special casing
> of builtins is IMHO a rather annoying but truly long standing bug. The only
> necessary difference is that one of them contains byte code and the other
> doesn't, everything else should eventually be aligned.

I see your point. I think that modifying 'from_builtin' as I suggested in my previous comment is the right thing to do. I'll make a new patch soon.
msg209915 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-01 19:51
Attached is a minimal patch that does what I think you meant.
msg209918 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-01 21:48
Stefan,
Please try the attached patch (sig_cython_01.patch)
msg209954 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-02 07:01
I tried the third patch and it works, but when I write this into a docstring:

    def func(x, *, y=None):
        """sig=(a,b)"""

then it fails to extract the signature again and returns (a,b) instead.

I also tried putting in some math term as (non-signature) documentation:

    def sig(a, b):
        """sig=(a*b)"""

and it raises a ValueError telling me that the signature is invalid.

So, I still think it should prefer a full function interface over the risky
"__text_signature__" quirk whenever it can.

I also think it shouldn't raise an error if the docstring isn't something
that it can parse, but that's not part of this ticket, I guess.
msg209966 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-02-02 10:20
In case a little background would help: while developing support for '__text_signature__' I had to move the test and the from_builtin() call to the top.  It used to be more in the middle-ish.  I don't have notes specifically on why I moved it, but I dimly recall that if I didn't try from_builtin first, there were other approaches that would succeed on the callable, that approach would fail on a builtin, so it'd throw an exception, and from_builtin wouldn't get a chance to try.

Also, I assumed that anything that had a __text_signature__ wasn't going to have any other kind of signature information.  Therefore, if the object had a __text_signature__ attribute, then I already knew that's the best approach and it should definitely be first.

Also also, I remember specifically that the isinstance(type) code would fail builtin classes.  But there's no generic way (that I know of) to tell whether a class is a user class or a builtin class.  So I wanted from_builtin to try handling a class first before the isinstance(type) class.

Are there callables in CPython that have *both* a __text_signature__ *and* have signature information derivable from another source, where you *don't* want to use __text_signature__?


Stefan: yes, you can write any garbage you want after "sig=(" and the C function that detects signatures will still assume you have a text signature.  That reminds me of a good joke.

Patient: Doctor, it hurts whenever I move my arm in this funny way.  *demonstrates*
Doctor: Well, then, don't do that.

I would be very interested if you knew of docstrings in the wild that innocently start with "sig=(" and yet aren't intended to be text signatures compatible with inspect.Signature.
msg209968 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-02 11:14
> I assumed that anything that had a __text_signature__ wasn't going
> to have any other kind of signature information.  Therefore, if the
> object had a __text_signature__ attribute, then I already knew that's
> the best approach and it should definitely be first.

That's likely true for CPython itself, at least currently. I don't think
it's generally a safe assumption in the CPython ecosystem, which includes
things like Cython, Numba, Nuitka and others, i.e. a growing number of
tools that can create function-like native callables with a signature that
is worth introspecting. They may or may not provide a function-like
signature, but if they do (and at least Cython and Nuitka provide one),
then that's the best you can get. Reducing it to a string representation,
especially now that we have annotations for Python signatures, sounds like
a major step backwards.

> Are there callables in CPython that have *both* a __text_signature__
> *and* have signature information derivable from another source, where
> you *don't* want to use __text_signature__?

Anything that inherits from PyCFunction will inherit "__text_signature__"
automatically. I should mention that this inheritance is actually not
allowed, according to the type flags in PyCFunction_Type. It's just that
Cython has been doing it that way for years, mainly because there are some
barriers in CPython (don't remember which) that prevent other types from
doing similar things.

> I would be very interested if you knew of docstrings in the wild that
> innocently start with "sig=(" and yet aren't intended to be text
> signatures compatible with inspect.Signature.

I agree that it's an unlikely start for a docstring. That's why it was
chosen, after all.

Safer ways to do it would be extending the type, or adding a flag in some
way, but that's going to a) hurt more than the current situation, and b)
become wasteful at some point when the __text_signature__ actually gets
replaced by a proper function interface for CPython builtins. The fact that
that wasn't doable for Py3.4 any more doesn't mean it shouldn't be done at all.
msg209972 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-02 11:42
Ok, I think I figured it out now.

Essentially, Cython's functions type, despite starting with the same struct layout as PyCFunction, must not visibly inherit from PyCFunction. Consequently, inspect.isbuiltin() returns False, as does inspect.isfunction() - sadly.

With Yury's changes that are currently committed, this triggers the fallback that checks for the function-like signature, which in turn makes Signature.from_function() properly analyse the signature, including default arguments, annotations, etc.

So, nothing left to change on CPython side for this ticket. And thanks again for the help. Closing as fixed.
msg210003 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-02 17:53
Stefan,

> inspect.isbuiltin() returns False

Are you absolutely sure about this? Please check that everything works with this latest change http://hg.python.org/cpython/rev/48c3c42e3e5c
msg210004 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-02 17:56
Larry,

> Also, I assumed that anything that had a __text_signature__ wasn't going to have any other kind of signature information.  Therefore, if the object had a __text_signature__ attribute, then I already knew that's the best approach and it should definitely be first.

I think that __text_signature__ is similar to the __signature__ attribute, i.e. if an object has it, then it should blindly use it, without trying anything else (where __signature__ should have a higher priority than __text_signature__)
msg210051 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-02-02 23:22
> That's likely true for CPython itself, at least currently.
> I don't think it's generally a safe assumption in the CPython
> ecosystem, which includes things like Cython, Numba, Nuitka and others,

While I'm happy to accommodate these other projects if it's easy to do
so, my primary concern is what's best for CPython.


> Reducing it to a string representation, especially now that we
> have annotations for Python signatures, sounds like a major step
> backwards.

What nonsense.  CPython has never had signature information for
builtins before.  And, the format of __text_signature__ allows
expressing a signature that uses every feature available in
inspect.Signature.  This cannot legitimately be called a "major step
backwards".

Also, PEP 8 forbids using annotations in the CPython library, which
includes all of CPython's builtins.  So using annotations in any way
for this was off the table.


> Safer ways to do it would be extending the type, or adding a flag
> in some way, but that's going to a) hurt more than the current
> situation, and b) become wasteful at some point when the
> __text_signature__ actually gets replaced by a proper function
> interface for CPython builtins. The fact that that wasn't doable
> for Py3.4 any more doesn't mean it shouldn't be done at all.

You imply that __text_signature__ is improper and unsafe.  We debated
how to communicate static signature information from C to Python at
runtime, and this was easily the best--most expressive, most
compact.  So it is entirely proper.  Hiding it in the docstring was
a cheap hack, I'll admit, but with an appropriately unlikely signature
('sig=') it seems perfectly safe.

If in the future we need to change how we represent signatures, we can
remove it.  It's an undocumented internal implementation detail and we
have free license to do so.  So even if we changed our mind in the
future I don't see how it would be "wasteful".
msg210081 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-03 07:06
> Also, PEP 8 forbids using annotations in the CPython library, which
> includes all of CPython's builtins.  So using annotations in any way
> for this was off the table.

Interesting, wasn't aware of that. Then let's wait what will happen when
users start manually adding signature information to the docstrings of
their C code extensions and come asking for annotation support in order to
allow for introspection of the expected parameter types.

> You imply that __text_signature__ is improper and unsafe.

What I'm saying is that the existing function introspection API would have
provided a much better way to do these things, and that it's good to
finally have the meta data available in the source code so that that API
can be made available at some point. Sorry if my wording sounded offensive.

> If in the future we need to change how we represent signatures, we can
> remove it.  It's an undocumented internal implementation detail and we
> have free license to do so.

I don't think that's possible any more once it's out in the wild. The
feature is just way too visible.
msg210084 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-03 07:17
>> inspect.isbuiltin() returns False
> Are you absolutely sure about this?

Yes. The "inheritance" of Cython's function type from PyCFunction is a pure
implementation detail of the object struct layout that is not otherwise
visible in any way. Specifically, there is no base type. The only reason
for the identical (start of the) struct layout is to allow reusing some of
the normal PyCFunction C-API functions on it instead of having to copy them
into Cython.

Cython's functions are neither instances of PyFunction nor of PyCFunction.
They implement the interface of PyFunction, though.

> http://hg.python.org/cpython/rev/48c3c42e3e5c

This change is redundant since BuiltinFunctionType (which isbuiltin() tests
for) is already in _NonUserDefinedCallables, which is tested for right
afterwards.
msg210085 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-03 07:43
> This change is redundant since BuiltinFunctionType (which isbuiltin() tests
> for) is already in _NonUserDefinedCallables, which is tested for right
> afterwards.

The code is more understandable, though.
Anyways, I'm glad that the issue is now resolved.

Also, please let's stop posting anything unrelated in this issue.
msg210099 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-02-03 08:45
> What I'm saying is that the existing function introspection API
> would have provided a much better way to do these things,
> and that it's good to finally have the meta data available in the
> source code so that that API can be made available at some point.

What "existing function introspection API"?  I wasn't aware there was an existing mechanism to provide signature metadata for builtin functions.
msg210101 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-03 09:00
> What "existing function introspection API"?  I wasn't aware there was an
> existing mechanism to provide signature metadata for builtin functions.

Not for builtin functions, but it's unclear to me why the API of builtin
functions should be different from that of Python functions (except, as I
said, for the existence of byte code).

I agree with Yury, however, that this discussion is unrelated to this ticket.
msg210103 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-02-03 09:49
> Not for builtin functions, but it's unclear to me why the API of
> builtin functions should be different from that of Python functions
> (except, as I said, for the existence of byte code).

I really don't follow you.  You seem to be saying that __text_signature__ is a bad idea, and keep talking about existing
APIs that provide for the same functionality, but you decline to name
specifics.

Be specific.  Let's say we remove __text_signature__.  How do we
now write a C extension in a way that we can have introspection
information for its callables?

If __text_signature__ is redundant with existing APIs, then we should remove it now before 3.4 ships.
msg210104 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-03 10:05
Python 3.4.0b3+ (default:19d81cc213d7, Feb  1 2014, 10:38:23)
[GCC 4.8.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def test(a,b,c=None): pass
>>> set(dir(test)) - set(dir(len))
{'__get__', '__code__', '__globals__', '__dict__', '__defaults__', '__kwdefaults__', '__annotations__', '__closure__'}
>>> test.__kwdefaults__
>>> test.__defaults__
(None,)
>>> test.__annotations__
{}
>>> test.__code__
<code object test at ..., file "<stdin>", line 1>
>>> dir(test.__code__)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> test.__code__.co_varnames
()
>>> test.__code__.co_varnames
()
>>> test.__code__.co_varnames
('a', 'b', 'c')
>>> test.__code__.co_kwonlyargcount
0
>>> test.__code__.co_name
'test'


But again, this is not covered by the subject of this ticket. I also don't think it's a good idea to delay Py3.4 until this discrepancy is fixed.
msg210105 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-03 10:07
"""
>>> test.__code__.co_varnames
()
>>> test.__code__.co_varnames
()
>>> test.__code__.co_varnames
('a', 'b', 'c')
"""

copy&pasto, please ignore the first two... :o)
msg210109 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-02-03 10:44
Yyou have just answered the question "How do you determine signature information for functions written in Python?".  A shorter way to express this answer: functions written in Python are implemented as a "function object" (in C, PyFunctionObject), which internally has a reference to a "code object" (PyCodeObject).  These two objects collectively contain all the information you'd need to determine the function's signature in Python.

However, builtin functions don't use either of these objects.  Builtin functions are implemented with a "builtin code object" (PyCFunctionObject) which doesn't have any of the metadata you cited.  So that doesn't answer my question.  Nor is it practical to implement a builtin function using a "function object" and a "code object".

So I'll ask you again:

> Be specific.  Let's say we remove __text_signature__.  How do we
> now write a C extension in a way that we can have introspection
> information for its callables?
msg210112 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-03 10:59
> [...] a "builtin code object" (PyCFunctionObject) [...] doesn't have any of the metadata you cited.

That exactly is the bug.

I think we should take the further discussion offline, or move it to a new ticket.
msg210116 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2014-02-03 11:38
Stefan is suggesting that rather than emulating the Python signature line
(which is a concise human readable notation that with the PEP 457 tweaks
will handle even the most obscure custom argument parsing, whether that
parsing is implemented in C or in Python), it would make more sense to
instead emulate the confusing jumble of CPython implementation details
exposed by Python level function and code objects. That ducktyping has been
the traditional way to support signature introspection for anyone that
didn't have the ability to change how the inspect module works.

However, I'm not sure *how* generating and storing an assortment of hard to
interpret lists and strings and mappings would qualify as being simpler
than generating and storing a single string that is only converted into
real signature data if requested. Once __text_signature__ becomes a public
API in 3.5 (as part of PEP 457), inspect shouldn't need to special case any
type ever again: it will be up to the type to set __text_signature__
properly instead.
msg210117 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-02-03 12:09
I understand Stefan to (reasonably) want 1 api instead of 2. He imagined that the only way to do that would be for everything to at least partially imitate the old methods. Larry and Nick are suggesting instead that everything should adopt the implementation-independent new method. Sounds right to me. So further discussion should be about PEP 457, elsewhere.
msg210122 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-03 12:53
> I understand Stefan to (reasonably) want 1 api instead of 2.

Absolutely. However, I guess the underlying reasoning here is that there
are other callables, too, so it's not worth making just two kinds of them
look the same, even if both are functions and even if both are part of
CPython itself.

As soon as there is easy-to-use support for annotations in C implemented
functions (for use outside of CPython and Cython), I'll agree that the need
for changing anything in CPython's builtin functions type isn't really
worth bothering about any more in the future, given that the Signature
object is the designated unifying API.
msg211174 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-13 21:37
>>> inspect.isbuiltin() returns False
>> Are you absolutely sure about this?
> Yes.

Oh, well...

isbuiltin(cyfunction) *does* return False. However, ismethoddescriptor(cyfunction) returns True, because Cython's functions bind as methods and thus have a "__get__()" (but no __set__()). Therefore, _signature_is_builtin(cyfunction) returns True, and the Signature.from_builtin(cyfunction) code path is taken and fails.

So, once again:

If the intention is to test that a callable has a "__text_signature__", then the code should test for the existance of "__text_signature__". It should *not* try to test for types that may or may not have such an attribute, and then just fail if it surprisingly does not find it.
msg211176 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-13 21:41
BTW, ismethoddescriptor() is an exceedingly broad test, IMHO. I wonder if it should even be relied upon for anything in inspect.py.
msg211181 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-02-13 22:09
Since this is a problem in Cython, not in CPython, maybe you can fix it in Cython?
msg211182 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-13 22:14
Patch sig_cython_03.patch is still applicable (it probably won't apply to the current repo, but I can fix that). It should solve all these problems once and for all.

If Larry agrees, I can still merge it in.
msg211183 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-13 22:19
> So, once again:

> If the intention is to test that a callable has a "__text_signature__", 
> then the code should test for the existance of "__text_signature__". 
> It should *not* try to test for types that may or may not have such 
> an attribute, and then just fail if it surprisingly does not find it.

Unfortunately this is something I do *not* want to change.
msg211184 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-13 22:33
> Since this is a problem in Cython, not in CPython, maybe you can fix it in Cython?

I'm actually considering that. Now that Signature.from_function() allows
function-like types, it appears like it's the easiest solution to add a
"__signature__" property to cyfunctions that does the necessary "from
inspect import Signature, return Signature.from_function(self)" dance.
msg211188 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-13 22:46
> I'm actually considering that. Now that Signature.from_function() allows
> function-like types, it appears like it's the easiest solution to add a
> "__signature__" property to cyfunctions that does the necessary "from
> inspect import Signature, return Signature.from_function(self)" dance.

Oh, sound like a big hack.

Stefan, Larry,

Please take a look at the new patch 'sig_cython_latest'

The change is really minimal, I think we can still push this in 3.4.
msg211191 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-13 23:43
> Oh, sound like a big hack.

Well, it's certainly a bunch of overhead (even assuming that "inspect" will
most likely be imported already - just looked it up in import.c, there's
lots of useless generic code there), with a lot of potential for something
going wrong, but it should still be somewhat acceptable. Certainly not time
critical. Having to maintain our own function type isn't exactly the most
simple way of going about it in the first place.

I just tried it, it adds some 20 lines of C code but works ok.

> Please take a look at the new patch 'sig_cython_latest'
> The change is really minimal, I think we can still push this in 3.4.

I'm certainly ok with it, given that I had already asked for that a while ago.
msg211891 - (view) Author: Roundup Robot (python-dev) Date: 2014-02-21 23:31
New changeset d6aa3fa646e2 by Yury Selivanov in branch 'default':
inspect.signature: Check for function-like objects before builtins. Issue #17159
http://hg.python.org/cpython/rev/d6aa3fa646e2
msg211892 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-21 23:32
Stefan,

I committed one more fix in signature -- now cython
functions should be finally supported.

I'm not sure if Larry accepts this in 3.4.0 though,
but I'll create an issue for that.
msg211895 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-02-21 23:37
I'm closing this issue. Please do not reopen it, and create a new one if necessary.
msg211910 - (view) Author: Larry Hastings (larry) * (Python committer) Date: 2014-02-22 07:32
Stefan: does the checkin in d6aa3fa646e2 fix your problem?  I'm willing to cherry-pick for this but I'd prefer to only have to do it once.
msg211914 - (view) Author: Stefan Behnel (scoder) * Date: 2014-02-22 08:08
I tested it and it works, so I could take the simple route now and say "yes, it fixes the problem", but it's actually no longer required because I already added a "__signature__" property to Cython's functions. However, as Yury noted, that's a hack because inspect.py can do the same thing way more efficiently with his latest change, so it allows me to reconsider and potentially get rid of it again.

Long story short, it works and does the right thing, so I'm happy to see this change go into Py3.4.
msg213829 - (view) Author: Roundup Robot (python-dev) Date: 2014-03-17 06:31
New changeset fa5127cdfe9d by Yury Selivanov in branch '3.4':
inspect.signature: Check for function-like objects before builtins. Issue #17159
http://hg.python.org/cpython/rev/fa5127cdfe9d
History
Date User Action Args
2014-03-17 06:31:08python-devsetmessages: + msg213829
2014-02-22 08:08:27scodersetmessages: + msg211914
2014-02-22 07:32:11larrysetmessages: + msg211910
2014-02-21 23:37:49yselivanovsetstatus: open -> closed

messages: + msg211895
2014-02-21 23:32:44yselivanovsetmessages: + msg211892
2014-02-21 23:31:21python-devsetmessages: + msg211891
2014-02-13 23:43:29scodersetmessages: + msg211191
2014-02-13 22:46:47yselivanovsetfiles: + sig_cython_latest_01.patch

messages: + msg211188
2014-02-13 22:33:03scodersetmessages: + msg211184
2014-02-13 22:19:12yselivanovsetmessages: + msg211183
2014-02-13 22:14:51yselivanovsetmessages: + msg211182
2014-02-13 22:09:54larrysetmessages: + msg211181
2014-02-13 21:41:51scodersetmessages: + msg211176
2014-02-13 21:37:41scodersetstatus: closed -> open

messages: + msg211174
2014-02-03 12:53:07scodersetmessages: + msg210122
2014-02-03 12:09:30terry.reedysetmessages: + msg210117
2014-02-03 11:38:41ncoghlansetmessages: + msg210116
2014-02-03 10:59:01scodersetmessages: + msg210112
2014-02-03 10:44:07larrysetmessages: + msg210109
2014-02-03 10:07:55scodersetmessages: + msg210105
2014-02-03 10:05:31scodersetmessages: + msg210104
2014-02-03 09:49:58larrysetmessages: + msg210103
2014-02-03 09:00:43scodersetmessages: + msg210101
2014-02-03 08:45:57larrysetmessages: + msg210099
2014-02-03 07:43:24yselivanovsetmessages: + msg210085
2014-02-03 07:17:21scodersetmessages: + msg210084
2014-02-03 07:06:26scodersetmessages: + msg210081
2014-02-02 23:22:32larrysetmessages: + msg210051
2014-02-02 17:56:23yselivanovsetmessages: + msg210004
2014-02-02 17:53:20yselivanovsetmessages: + msg210003
2014-02-02 11:42:14scodersetstatus: open -> closed

messages: + msg209972
2014-02-02 11:14:24scodersetmessages: + msg209968
2014-02-02 10:20:37larrysetmessages: + msg209966
2014-02-02 07:01:36scodersetmessages: + msg209954
2014-02-02 02:32:43yselivanovsetfiles: + sig_cython_03.patch
2014-02-01 22:09:16yselivanovsetfiles: + sig_cython_02.patch
2014-02-01 21:48:00yselivanovsetfiles: + sig_cython_01.patch

messages: + msg209918
2014-02-01 19:51:58scodersetfiles: + divert_from_builtin.patch

messages: + msg209915
2014-02-01 19:21:14yselivanovsetmessages: + msg209913
2014-02-01 19:07:20scodersetmessages: + msg209910
2014-02-01 18:39:24yselivanovsetmessages: + msg209907
2014-02-01 18:17:50yselivanovsetstatus: closed -> open
2014-02-01 16:20:48scodersetmessages: + msg209896
2014-01-31 20:22:10python-devsetmessages: + msg209827
2014-01-31 19:54:07scodersetmessages: + msg209824
2014-01-31 19:50:31yselivanovsetstatus: open -> closed
resolution: fixed
messages: + msg209822

stage: patch review -> resolved
2014-01-31 19:48:51python-devsetnosy: + python-dev
messages: + msg209821
2014-01-31 19:47:15yselivanovsetmessages: + msg209820
2014-01-31 19:33:46scodersetmessages: + msg209816
2014-01-31 19:19:44yselivanovsetfiles: + sig_func_ducktype_03.patch
keywords: + patch
2014-01-31 19:19:32yselivanovsetfiles: - sig_func_ducktype_03.patch
2014-01-31 19:18:47yselivanovsetkeywords: + needs review, - patch
files: + sig_func_ducktype_03.patch
messages: + msg209812
2014-01-31 13:22:14larrysetmessages: + msg209787
2014-01-31 12:07:35scodersetmessages: + msg209775
2014-01-30 17:28:30yselivanovsetfiles: + sig_func_ducktype_02.patch

nosy: + larry
messages: + msg209729

type: behavior -> enhancement
stage: patch review
2014-01-30 17:15:30scodersetmessages: + msg209728
2014-01-30 16:49:36yselivanovsetfiles: + sig_func_ducktype_01.patch
assignee: yselivanov
messages: + msg209726
2014-01-30 07:15:27scodersetmessages: + msg209708
2014-01-29 20:44:36yselivanovsetnosy: + yselivanov

messages: + msg209675
versions: - Python 3.3
2013-02-17 05:59:38scodersetfiles: + inspect_sig_3.patch

messages: + msg182262
2013-02-12 01:21:43terry.reedysetmessages: + msg181940
2013-02-11 09:46:44ncoghlansetmessages: + msg181887
2013-02-11 07:44:27scodersetmessages: + msg181879
2013-02-11 01:46:25terry.reedysetmessages: + msg181868
2013-02-10 13:56:13pitrousetnosy: + pitrou
messages: + msg181806
2013-02-10 13:53:07scodersetmessages: + msg181805
2013-02-08 21:44:12scodersetfiles: + inspect_sig_2.patch

messages: + msg181704
2013-02-08 21:20:10terry.reedysetnosy: + terry.reedy
messages: + msg181702
2013-02-08 16:02:55scodersetfiles: + inspect_sig_with_docstring.patch

messages: + msg181678
2013-02-08 15:55:24eric.araujosetnosy: + eric.araujo
messages: + msg181677
2013-02-08 15:50:07scodersetfiles: + inspect_sig.patch

nosy: + ncoghlan, benjamin.peterson
messages: + msg181676

keywords: + patch
2013-02-08 15:20:30scodercreate