faqts : Computers : Programming : Languages : Python : Common Problems

+ Search
Add Entry AlertManage Folder Edit Entry Add page to http://del.icio.us/
Did You Find This Entry Useful?

15 of 15 people (100%) answered Yes
Recently 10 of 10 people (100%) answered Yes

Entry

Is it possible to get the declaration of the argument-list of any function in the python engine? just like it is possible to get the func_name or doc-string.

Aug 30th, 2000 21:39
unknown unknown, Alex Martelli


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:-).