A Python Function to Describe another Function

Noob Alert

Updated: 12/14/2019

what were you thinking?
It turns out that I wasn’t aware of a nifty little built in function to do something very similar when I wrote this article. I wanted to update this post to provide details on that.

If you use the built-in help function you will get very similar results to my function below. It is nearly always smarter to use a built in vs build your own so I would suggest doing this instead of what I metnion below. That said I’ll leave this up for reference to the individual attributes which might be useful in other applications

print(help(download_wait))

Output

Help on function download_wait in module __main__:

download_wait(directory: str, *args, timeout: int = 0, file_count: int = 1) -> list
    Wait for downloads to finish with a specified timeout.
    
    Args
    ----
    directory : str
        The path to the folder where the files will be downloaded.
    timeout : int, defaults to 0 (indefinitely wait)
        How many seconds to wait until timing out.
    file_count : int, defaults to 1 (wait for 1 file)
        If provided, also wait for the expected number of files.
    
    Return
    ------
    New files found : list(str)

None

On to the original Article

Today’s entry will be a quick little function I put together to help me see information about other functions when I am using a Jupyter notebook. To start us off, I will define a function, which I found on Stackoverflow and modified to fit my needs to wait for a file download after using Selenium to download a file from behind a login page.

progress bar

import time
import os

def download_wait(directory : str,  *args, timeout : int =0, file_count : int=1) -> list:
    """
    Wait for downloads to finish with a specified timeout.

    Args
    ----
    directory : str
        The path to the folder where the files will be downloaded.
    timeout : int, defaults to 0 (indefinitely wait)
        How many seconds to wait until timing out.
    file_count : int, defaults to 1 (wait for 1 file)
        If provided, also wait for the expected number of files.

    Return
    ------
    New files found : list(str)
    """
    seconds = 0
    dl_wait = True
    orig_files=os.listdir(directory)
    orig_file_count = len(orig_files)
    while dl_wait and seconds < timeout if timeout>0 else dl_wait:
        time.sleep(1)
        dl_wait = False
        files = os.listdir(directory)
        if len(files) < orig_file_count+file_count:
            dl_wait = True

        for fname in list(set(files)-set(orig_files)):
            if fname.endswith('.crdownload'):
                dl_wait = True

        seconds += 1
    return list(set(files)-set(orig_files))

My version is similar to what was posted. The exception being mine returns a list of the new files, versus the seconds elapsed in the original. Additionally, my version changes the logic of file_count versus the original nfiles. In any case this isn’t the point of my article. It is merely a function to demonstrate the functionality of the describe function, which I pulled together.

progress bar

def get_function_info(fnc):
    """
    returns a formatted string with details about the function provided.
    
    Args
    ----
    fnc : function
        The function to describe
        
    Return
    ------
    description of the function : str
    """
    out = "No Information available."
    try:
        out = "{}\n\n{}\n{}".format(fnc.__name__,str({k : str(v.__name__) +
                    "=" + str(fnc.__kwdefaults__.get(k)) 
                    for k,v in fnc.__annotations__.items()})[1:-1].replace("'","").
                    replace("=None", "").strip() if hasattr(fnc, '__annotations__') else  '', "".join([(line[4:] if line[:4]=='    ' else line) +'\n' for line in fnc.__doc__.split('\n')]) if not fnc.__doc__ == None else '').strip()
    except Exception as e:
        out = "No information available: {}".format(str(e))
    finally:
        return out

This function will accept a function as it’s only argument. It returns a string that provides a nicely formatted description of the function. The string includes the function name. If the function has an annotations attribute, it will also include a descriptive string of its arguments and return values. Finally, it prints the docstring from the function (if present). If the docstring happens to be indented four spaces (which many of my own tend to be), it removes those four leading spaces).

The function has some limitations, possibly due to limitations in my own understanding of Python and its standard library (I’m still really new at this). Some of these limitations are:

  • If a default value is provided for an argument which is positional, it won’t be displayed
  • Many functions don’t have an annoations attribute, especially built-in functions. So the information is limited.
  • Some built-in functions, like exit, tend to give me errors.

In any case, here is an example of the output when it works the way it should, based on the download_wait function above.

print(get_function_info(download_wait))
download_wait

directory: str, timeout: int=0, file_count: int=1, return: list

Wait for downloads to finish with a specified timeout.

Args
----
directory : str
    The path to the folder where the files will be downloaded.
timeout : int, defaults to 0 (indefinitely wait)
    How many seconds to wait until timing out.
file_count : int, defaults to 1 (wait for 1 file)
    If provided, also wait for the expected number of files.

Return
------
New files found : list(str)

I find it to be a useful function to add to a notebook to help me while I’m working – and it was fun to play with these inspect attributes. You can see more about these attributes in the Inspect live objects documentation page.