[Python-Dev] inspect.getargspec()

Patrick K. O'Brien pobrien@orbtech.com
Wed, 6 Nov 2002 18:54:19 -0600


I was patching up some bugs in the inspect module, and I've reached a 
particular bug where I'd like some advice. The bug on SF is # 620190 and it 
relates to the fact that inspect.getargspec() fails when called with a 
method, rather than a function. I've got the code that fixes this (it's 
actually been in PyCrust for a while) but fixing it also changes the 
semantics, because getargspec() is clear that it only works on functions. 
Of course, expanding getargspec() to work on any callable is fairly 
trivial, so I see no reason why it should be limited to functions.

In any case, I'll outline some of the issues below and post the code I've 
been using in PyCrust. I'm hoping that someone from the inner circle will 
work with me to update the module, update the unit tests, and check the 
changes in (or create patches). I'd hate to work up a patch just to find 
out that my solution wasn't appropriate.

The main issues are:

* Is there any reason to not expand the scope of getargspec()?
* For builtin functions whose arguments cannot be determined (at least not 
that I know of) what should getargspec() do? Should it raise an error or 
return empty values?
* For a bound method, should getargspec() include the first argument 
(usually self) since Python passes that value implicitly? Perhaps there 
should be a optional argument to toggle this.

Here is what the current getargspec() looks like:

def getargspec(func):
    """Get the names and default values of a function's arguments.

    A tuple of four things is returned: (args, varargs, varkw, defaults).
    'args' is a list of the argument names (it may contain nested lists).
    'varargs' and 'varkw' are the names of the * and ** arguments or None.
    'defaults' is an n-tuple of the default values of the last n 
arguments."""
    if not isfunction(func): raise TypeError, 'arg is not a Python function'
    args, varargs, varkw = getargs(func.func_code)
    return args, varargs, varkw, func.func_defaults

Here is what I use in PyCrust to get the argument spec for any callable (At 
least the ones that have an argspec. Builtin functions are still an issue.)

Basically, I take the object in question and run it through the following 
function to get an object that will work with inspect.getargspec(). I'm 
also determining whether I should drop the first argument (self) when I 
display the calltip for this object, since Python passes that implicitly on 
bound methods:

def getBaseObject(object):
    """Return base object and dropSelf indicator for an object."""
    if inspect.isbuiltin(object):
        # Builtin functions don't have an argspec that we can get.
        dropSelf = 0
    elif inspect.ismethod(object):
        # Get the function from the object otherwise inspect.getargspec()
        # complains that the object isn't a Python function.
        try:
            if object.im_self is None:
                # This is an unbound method so we do not drop self from the
                # argspec, since an instance must be passed as the first 
arg.
                dropSelf = 0
            else:
                dropSelf = 1
            object = object.im_func
        except AttributeError:
            dropSelf = 0
    elif inspect.isclass(object):
        # Get the __init__ method function for the class.
        constructor = getConstructor(object)
        if constructor is not None:
            object = constructor
            dropSelf = 1
        else:
            dropSelf = 0
    elif callable(object):
        # Get the __call__ method instead.
        try:
            object = object.__call__.im_func
            dropSelf = 1
        except AttributeError:
            dropSelf = 0
    else:
        dropSelf = 0
    return object, dropSelf

def getConstructor(object):
    """Return constructor for class object, or None if there isn't one."""
    try:
        return object.__init__.im_func
    except AttributeError:
        for base in object.__bases__:
            constructor = getConstructor(base)
            if constructor is not None:
                return constructor
    return None

And here is a snippet of the code that makes use of the previous functions, 
just for completeness:

    name = ''
    object, dropSelf = getBaseObject(object)
    try:
        name = object.__name__
    except AttributeError:
        pass
    tip1 = ''
    argspec = ''
    if inspect.isbuiltin(object):
        # Builtin functions don't have an argspec that we can get.
        pass
    elif inspect.isfunction(object):
        # tip1 is a string like: "getCallTip(command='', locals=None)"
        argspec = apply(inspect.formatargspec, inspect.getargspec(object))
        if dropSelf:
            # The first parameter to a method is a reference to an
            # instance, usually coded as "self", and is usually passed
            # automatically by Python and therefore we want to drop it.
            temp = argspec.split(',')
            if len(temp) == 1:  # No other arguments.
                argspec = '()'
            else:  # Drop the first argument.
                argspec = '(' + ','.join(temp[1:]).lstrip()
        tip1 = name + argspec

Thanks in advance for the help.

-- 
Patrick K. O'Brien
Orbtech      http://www.orbtech.com/web/pobrien
-----------------------------------------------
"Your source for Python programming expertise."
-----------------------------------------------