This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: inspect.getargspec() returns wrong answer with datetime.today.__call__()
Type: Stage:
Components: Library (Lib) Versions: Python 3.4
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: Arfrever, larry, ncoghlan, yselivanov, zzzeek
Priority: normal Keywords:

Created on 2014-03-02 15:10 by zzzeek, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (6)
msg212556 - (view) Author: mike bayer (zzzeek) * Date: 2014-03-02 15:10
this appears like it may be related to http://bugs.python.org/issue20786, at least in terms of inspect.getargspec() seems to be returning answers in 3.4 where it used to raise TypeError, however like in 20786 it's again returning the wrong answer.  I'm a little concerned that some change was made to allow inspection of lots of builtins that wasn't before, but the argument lists haven't been checked.

test case:

import inspect
import datetime

try:
    insp = inspect.getargspec(datetime.datetime.today.__call__)
except TypeError:
    pass
else:
    print(insp)
    assert insp == (["self"], "args", "kwargs", None)

    # claims to accept "args" and "kwargs", ok let's try...
    datetime.datetime.today.__call__(1, 2, foo='bar')

    # raises:
    # TypeError: today() takes no keyword arguments
msg212562 - (view) Author: mike bayer (zzzeek) * Date: 2014-03-02 15:38
I've also worked around this on my end, so if my poking into today.__call__() isn't really a good idea anyway, I'm not doing that anymore.
msg212563 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-03-02 17:12
Why are you trying to get signature of 'datetime.datetime.today.__call__'?

__call__ in this case is a generic python object -- method-wrapper. It is used to make a classmethod. And the signature that getargspec is returning for it is correct.
msg212568 - (view) Author: mike bayer (zzzeek) * Date: 2014-03-02 18:07
we basically need to be able to get the argument signature for anything that passes callable(); function, method, class, object with __call__.  So this is the logic we use:

import inspect

def get_callable_argspec(fn):
    if inspect.isfunction(fn) or inspect.ismethod(fn):
        inspectable = fn
    elif inspect.isclass(fn):
        inspectable = fn.__init__
    elif hasattr(fn, '__call__'):
        inspectable = fn.__call__
    else:
        inspectable = fn

    try:
        return inspect.getargspec(inspectable)
    except TypeError:
        raise

def callable1(self, x, y):
    pass

class SomeClass(object):
    def __init__(self, x, y):
        pass

    def callable2(self, x, y):
        pass

    def __call__(self, x, y):
        pass

callable3 = SomeClass
callable4 = SomeClass(2, 3)

for callable_ in (callable1, SomeClass(1, 2).callable2, callable3, callable4):
    assert callable(callable_)  # the interpreter can tell me this

    # how can it reliably tell me this? 
    assert get_callable_argspec(callable_) == (["self", "x", "y"], None, None, None)


If you pass a builtin like datetime.datetime.today to it, isfunction()/ismethod()/isclass() return false, but it does have a __call__().

I'm working around this now by just refusing to act on anything that is types.BuiltinMethodType or types.BuiltinFunctionType.   

Any guidance on what the proper way is to get the argument signature for any object that returns True for callable() would be very helpful (py2.6-3.x compatible).  I'm not sure if there's a stdlib call for this.
msg212591 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-03-02 21:17
OK, I see.

I'd recommend you to take a look how inspect.signature is implemented in 3.3 or 3.4 (and maybe backport it to python 2 and use the new API).

To quickly fix your code, I'd suggest the following modifications:

_WrapperDescriptor = type(type.__call__)
_MethodWrapper = type(all.__call__)
_ClassMethodWrapper = type(int.__dict__['from_bytes'])

def get_callable_argspec(fn):
    if inspect.isfunction(fn) or inspect.ismethod(fn):
        inspectable = fn
    elif inspect.isclass(fn):
        inspectable = fn.__init__
    elif hasattr(fn, '__call__'):
        inspectable = fn.__call__
    else:
        inspectable = fn
   
    if isinstance(fn, (_WrapperDescriptor, _MethodWrapper, _ClassMethodWrapper)):
        raise ValueError('unsupported callable {!r}'.format(fn))

    try:
        return inspect.getargspec(inspectable)
    except TypeError:
        raise


I'm closing this issue, as there is no real bug or regression in getargspec.
msg212595 - (view) Author: mike bayer (zzzeek) * Date: 2014-03-02 22:04
I've got something like that going on right now, but am doing it in the other direction; when I look at __call__(), I only do anything with it if it passes inspect.ismethod().  Since I only want plain Python __call__() functions on plain Python objects.
History
Date User Action Args
2022-04-11 14:57:59adminsetgithub: 65027
2014-03-02 22:04:51zzzeeksetmessages: + msg212595
2014-03-02 21:17:12yselivanovsetstatus: open -> closed
resolution: not a bug
messages: + msg212591
2014-03-02 19:52:05Arfreversetnosy: + Arfrever
2014-03-02 18:07:40zzzeeksetmessages: + msg212568
2014-03-02 17:13:04yselivanovsetnosy: + ncoghlan
2014-03-02 17:12:51yselivanovsetnosy: + yselivanov
messages: + msg212563
2014-03-02 15:38:39zzzeeksetmessages: + msg212562
2014-03-02 15:19:26r.david.murraysetnosy: + larry
2014-03-02 15:10:47zzzeekcreate