Get parameter(arg) count of builtin functions in Python

后端 未结 1 1885
北荒
北荒 2021-01-06 05:09

I wrote my own c-module for Python and for a custom table in a documentation I need the number of parameters of the builtin-functions during runtime.

<
相关标签:
1条回答
  • 2021-01-06 05:56

    In the following this is the working solution I came up with for Python 2 and 3.

    What does it do?

    During runtime a list of 99 None objects gets passed to the corresponding function. One of the first checks in the internal parsing function PyArg_ParseTuple checks if the amount of parameters matches the amount of passed parameters - if not it will fail. That means we will call the function but we can also be sure it doesn't get really executed.

    Technical background:

    Why is it so hard to get the count of parameters of built-in functions? The problem is that the parameter list is evaluated during runtime, not compile time. A very simple example of a built-in function in C looks like this:

    static PyObject* example(PyObject *self, PyObject *args)
    {
        int myFirstParam;
        if(!PyArg_ParseTuple(args, "i", &myFirstParam))
            return NULL;
        ...
    }
    

    Copy and Paste Solution:

    import inspect
    import time
    import re
    import types
    import sys
    
    
    def get_parameter_count(func):
        """Count parameter of a function.
    
        Supports Python functions (and built-in functions).
        If a function takes *args, then -1 is returned
    
        Example:
            import os
            arg = get_parameter_count(os.chdir)
            print(arg)  # Output: 1
    
        -- For C devs:
        In CPython, some built-in functions defined in C provide
        no metadata about their arguments. That's why we pass a
        list with 999 None objects (randomly choosen) to it and
        expect the underlying PyArg_ParseTuple fails with a
        corresponding error message.
        """
    
        # If the function is a builtin function we use our
        # approach. If it's an ordinary Python function we
        # fallback by using the the built-in extraction
        # functions (see else case), otherwise
        if isinstance(func, types.BuiltinFunctionType):
            try:
                arg_test = 999
                s = [None] * arg_test
                func(*s)
            except TypeError as e:
                message = str(e)
                found = re.match(
                    r"[\w]+\(\) takes ([0-9]{1,3}) positional argument[s]* but " +
                    str(arg_test) + " were given", message)
                if found:
                    return int(found.group(1))
    
                if "takes no arguments" in message:
                    return 0
                elif "takes at most" in message:
                    found = re.match(
                        r"[\w]+\(\) takes at most ([0-9]{1,3}).+", message)
                    if found:
                        return int(found.group(1))
                elif "takes exactly" in message:
                    # string can contain 'takes 1' or 'takes one',
                    # depending on the Python version
                    found = re.match(
                        r"[\w]+\(\) takes exactly ([0-9]{1,3}|[\w]+).+", message)
                    if found:
                        return 1 if found.group(1) == "one" \
                                else int(found.group(1))
            return -1  # *args
        else:
            try:
                if (sys.version_info > (3, 0)):
                    argspec = inspect.getfullargspec(func)
                else:
                    argspec = inspect.getargspec(func)
            except:
                raise TypeError("unable to determine parameter count")
    
            return -1 if argspec.varargs else len(argspec.args)
    
    
    
    def print_get_parameter_count(mod):
        for x in dir(mod):
            e = mod.__dict__.get(x)
            if isinstance(e, types.BuiltinFunctionType):
                print("{}.{} takes {} argument(s)".format(mod.__name__, e.__name__, get_parameter_count(e)))
    
    import os
    print_get_parameter_count(os)
    

    Output:

    os._exit takes 1 argument(s)
    os.abort takes 0 argument(s)
    os.access takes 2 argument(s)
    os.chdir takes 1 argument(s)
    os.chmod takes 2 argument(s)
    os.close takes 1 argument(s)
    ...
    
    0 讨论(0)
提交回复
热议问题