how to get parameter decl of a function?

Alex Martelli alex at magenta.com
Mon Aug 28 07:23:37 EDT 2000


"Robert Kiendl" <rkiendl at gmx.net> wrote in message
news:39AA3F24.BF4EC75 at gmx.net...
> is it possible to get the declaration of the the argument-list of any
> function in the python engine? just like it is possible to get the
> func_name or doc-string.

Yes; a very good packaging of this you can find in Lemburg's module 'hack',
see http://starship.python.net/crew/lemburg/hack.py.

Don't get put off by the module's name -- it's just a collection of
"tools that I find useful to examine code from inside an interactive
interpreter session", as the author says!

The functionality you want is packaged as part of function
func_sig of this module.

It is, of course, instructive to examine exactly what this function
does.  First of all:

def func_sig(func):
    if hasattr(func,'im_func'):
        func = func.im_func

This takes care of the case where 'func' is not really a function, but
rether a method; in this case, the real function is its attribute im_func,
so this part "normalizes" this case back to the general case.  Then:

    code = func.func_code
    fname = code.co_name
    callargs = code.co_argcount
    args = code.co_varnames[:callargs]

Now we have the function name (fname), and the arguments' names (args).

With just this part, we can already, in a sense, satisfy your request:

def func_args(func):
    if hasattr(func,'im_func'):
        func = func.im_func
    code = func.func_code
    fname = code.co_name
    callargs = code.co_argcount
    args = code.co_varnames[:callargs]
    return "%s(%s)" % (fname, string.join(args,','))

and now for example:

>>> fa.func_args(string.join)
'join(words,sep)'
>>>

What we're still missing are the default values of arguments that do
have them -- here, for example, the fact that the 'sep' argument can
be omitted, because it defaults to space.  And also, the ability of
some functions to accept unlimited lists of arguments (the *args form,
and/or **kw for keyword-arguments).  Lemburg goes into slightly deeper
waters to add this information, too...:

    args = list(code.co_varnames[:callargs])

...list, not tuple, because we may want to change some elements...

    if func.func_defaults:
        i = len(args) - len(func.func_defaults)
        for default in func.func_defaults:
            try:
                r = repr(default)
            except:
                r = '<repr-error>'
            if len(r) > 100:
                r = r[:100] + '...'

...repr is used to get the string with which to represent the
    default value, with due care for possible errors, and/or
    too-long representations, which get truncated to 100 chars
    (with an ellipsis ... to remind the reader they're truncated)

            arg = args[i]
            if arg[0] == '.':
                # anonymous arguments
                arg = '(...)'
            args[i] = '%s=%s' % (arg,r)
            i = i + 1

I don't know what "anonymous arguments" are -- I'm learning of
their very existence by studying this code (talk about instructive!-)

    if code.co_flags & 0x0004: # CO_VARARGS
        args.append('*'+code.co_varnames[callargs])
        callargs = callargs + 1
    if code.co_flags & 0x0008: # CO_VARKEYWORDS
        args.append('**'+code.co_varnames[callargs])
        callargs = callargs + 1

As Lemburg notes in his comments, these constants are taken right
from the Python compiler's include file.

    return '%s(%s)' % (fname,string.join(args,', '))

At last -- the nicely formatted result!

>>> func_sig(string.join)
"join(words, sep=' ')"
>>>


Just one caveat: this will not work for functions written in C!
The "signature" is not available in this case by "introspection"
in this way; to see the difference:

>>> import string
>>> import strop
>>> dir(string.join)
['__doc__', '__name__', 'func_code', 'func_defaults', 'func_doc',
'func_globals', 'func_name']
>>> dir(strop.join)
['__doc__', '__name__', '__self__']
>>>

i.e., the Python-written 'string.join' wrapper offers the various
func_code, etc, members upon which this introspection can be
based; the bare-C 'strop.join' underlying level doesn't, so,
no real introspection -- you have to rely on the __doc__:

>>> print strop.join.__doc__
join(list [,sep]) -> string
joinfields(list [,sep]) -> string

Return a string composed of the words in list, with
intervening occurences of sep.  Sep defaults to a single
space.

(join and joinfields are synonymous)
>>>

Not too bad, after all:-).


Alex






More information about the Python-list mailing list