【问题标题】:Get parameter(arg) count of builtin functions in Python获取Python中内置函数的参数(arg)计数
【发布时间】:2018-07-12 02:01:16
【问题描述】:

我为 Python 和文档中的自定义表编写了自己的 c 模块我需要运行时内置函数的参数数量

Python 2 中的函数(如 inspect.getargspec)或 Python 3 中的函数(如 inspect.signature)支持普通的 Python 函数,但不支持内置函数。

目前还有另外两个社区解决方案:

  • 解析文档字符串
  • 解析原始 *.c 文件
  • 查看第三种方法的答案

在某些情况下,文档字符串已经过时和/或很难提取参数计数,因为文档字符串可以是任何纯字符串。解析原始 *.c 文件也是一种好方法,但您可能无法访问它。

【问题讨论】:

  • “我需要在运行时获取 Python 内置函数的参数个数” - 为什么?上下文可能会帮助人们提出其他方法。
  • @jonrsharpe 没错。这似乎有点像XY 问题,OP。
  • 我不知道 OP 需要这个做什么,但我写了很多依赖自省的神奇代码,我觉得这很有用。很难为此命名一个特定的用例,但 IMO 仍然是一个有趣的问题 - 当你进行大量的内省工作时,你永远不知道它什么时候可以派上用场:)
  • 所以这些不是内置的,它们是你写的函数?在那种情况下,如果这是您需要的信息,您为什么不编写一致的文档字符串?
  • 如前所述,文档字符串在某些情况下完全没问题,但我需要一种可靠的方法,该方法也适用于文档字符串可能不同或什至不提供的其他第 3 方 c 模块你需要的信息

标签: python python-3.x python-2.7 parameters


【解决方案1】:

以下是我为 Python 2 和 3 提出的工作解决方案。

它有什么作用?

在运行时,一个包含 99 个 None 对象的列表被传递给相应的函数。内部解析函数PyArg_ParseTuple 中的第一个检查是检查参数的数量是否与传递的参数的数量相匹配——如果不匹配,它将失败。这意味着我们将调用该函数,但我们也可以确保它没有真正执行。

技术背景:

为什么很难获得内置函数的参数数量?问题是参数列表是在运行时评估的,而不是编译时。 C 中内置函数的一个非常简单的示例如下所示:

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

复制粘贴解决方案:

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)

输出:

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

【讨论】:

  • 那么关键字参数呢?
  • 任意数量的参数也意味着数字未定义,您可以将引发的 TypeError 更改为 -1 或预期的其他值来识别它
猜你喜欢
  • 2021-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-04
  • 1970-01-01
  • 2020-04-17
  • 2011-08-31
  • 1970-01-01
相关资源
最近更新 更多