Python 自省:得到一个method_descriptor的参数列表?

Python introspection: get the argument list of a method_descriptor?

作为我的问题介绍的代码插图:

import re, inspect, datetime

inspect.getargspec (re.findall)
# =>
# ArgSpec(args = ['pattern', 'string', 'flags'], varargs=None,
# keywords=None, defaults = (0,))

type (datetime.datetime.replace)
# => <type 'method_descriptor'>

inspect.getargspec (datetime.datetime.replace)
# => Traceback (most recent call last):
#      File "<stdin>", line 1, in <module>
#      File "/usr/lib/python2.7/inspect.py", line 816, in getargspec
#        raise TypeError('{!r} is not a Python function'.format(func))
# TypeError: <method 'replace' of 'datetime.datetime' objects> is
# not a Python function

似乎我在编码时找到 datetime.datetime.replace 签名的唯一方法是在 the doc 中查找它:date.replace(year, month, day).

唯一似乎有效的内省部分:

datetime.datetime.replace.__doc__
# => 'Return datetime with new specified fields.'

我检查了 Jupyter 函数 arglist 工具提示的工作原理,它们有完全相同的问题,即 datetime.datetime.replace 没有可用的 arglist。

下面是问题:

  1. 是否仍然可以通过某种方式获取参数列表?也许我可以为 datetime 安装 C 源代码并通过 __file__ 属性连接它们?

  2. 是否可以用arglist信息注释一个<type 'method_descriptor'>?在那种情况下,我可以解析链接文档的降价定义并自动注释内置模块函数。

您遇到的问题是由 C-coded 函数未公开其签名这一事实引起的。您会找到有关此 answer to "How to find out the arity of a method in Python".

的更多信息

在您的例子中,re.findall 是在 Python 中定义的(参见 def findall(pattern, string, flags=0):) while datetime.datetime.replace is written in C (see datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw))。

您可以看到在具有 dir 内置函数的函数上使用不同的可用属性(尤其是 __code__ 属性):

>>> dir(datetime.datetime.replace)
['__call__', '__class__', '__delattr__', '__doc__', '__format__', '__get__', '__getattribute__', '__hash__', '__init__', '__name__', '__new__', '__objclass__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> dir(re.findall)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
>>> datetime.datetime.replace.__code__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'method_descriptor' object has no attribute '__code__'
>>> re.findall.__code__
<code object findall at 0x7fe7234e74b0, file "/usr/lib/python2.7/re.py", line 173>

通常,help 可以满足您的需求(基于 __doc__ 属性),但在您的情况下,它似乎帮助不大:

>>> help(datetime.datetime.replace)
Help on method_descriptor:

replace(...)
    Return datetime with new specified fields.

此外,一个想法可能是尝试将 __code__ 属性设置为符合您需要的东西,但 you can't tweak much on builtin types without subclassing.

不,您无法获得更多信息;安装 C 源代码不会让您轻松访问它们。那是因为 C 代码中定义的 大多数 方法实际上并不公开此信息;你必须解析出 rather cryptic piece of C code:

if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO$i:replace",
                                  datetime_kws,
                                  &y, &m, &d, &hh, &mm, &ss, &us,
                                  &tzinfo, &fold))

re.findall() 函数是一个 pure Python function,因此是可自省的。

我说 大多数 方法定义在 C 中,因为从 Python 3.4 及更高版本开始,使用新 Argument Clinic preprocessor will include a new __text_signature__ attribute, which the internal inspect._signature_fromstr() function 的方法可以解析。这意味着即使对于这样的 C-defined 方法,您也可以反省参数:

>>> import io
>>> import inspect
>>> type(io.BytesIO.read)
<class 'method_descriptor'>
>>> inspect.signature(io.BytesIO.read)
<Signature (self, size=None, /)>

另见 What are __signature__ and __text_signature__ used for in Python 3.4

datetime 模块尚未受到 Argument Clinic 的喜爱。我们必须耐心等待,或者如果您真的很关心这个,请提供将模块转换为使用 Argument Clinic 的补丁。

如果您想查看 do 已经支持哪些模块,请查看 Modules/clinic subdirectory which contains the generated clinic output; for the datetime module, only datetime.datetime.now() is currently included. That method defines a clinic block:

/*[clinic input]
@classmethod
datetime.datetime.now
    tz: object = None
        Timezone object.
Returns new datetime object representing current time local to tz.
If no tz is specified, uses local timezone.
[clinic start generated code]*/

static PyObject *
datetime_datetime_now_impl(PyTypeObject *type, PyObject *tz)
/*[clinic end generated code: output=b3386e5345e2b47a input=80d09869c5267d00]*/

使方法可自省:

>>> import datetime
>>> inspect.signature(datetime.datetime.now)
<Signature (tz=None)>

没有办法直接将信息附加到那些不可自省的C函数和方法;他们也不支持属性。

大多数想要支持此类对象的自动完成解决方案都使用单独的数据结构,其中信息是独立维护的(存在数据不同步的所有固有风险)。其中一些可用于您自己的目的:

  • Komodo IDE 代码智能库(开源,也用过其他编辑器)使用 CIX format to encode this data; you could download the Python 3 catalog。不幸的是,对于您的具体示例,datetime.replace() 函数签名尚未充实 :

    <scope doc="Return datetime with new specified fields." ilk="function" name="replace" />
    
  • 新的 Python 3.5 类型提示语法还需要知道对象期望的参数类型,为此需要为无法自省的对象提供存根文件. Python typeshed project provides these. This includes all argument names for the datetime module:

    class datetime:
        # ...
        def replace(self, year: int = ..., month: int = ..., day: int = ..., hour: int = ...,
            minute: int = ..., second: int = ..., microsecond: int = ..., tzinfo:
            Optional[_tzinfo] = None) -> datetime: ...
    

    你必须自己解析这样的文件;它们不能总是作为尚未定义的存根引用类型导入,而不是使用 forward references:

    >>> import importlib.machinery
    >>> path = 'stdlib/3/datetime.pyi'
    >>> loader = importlib.machinery.SourceFileLoader('datetime', path)
    >>> loader.load_module()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<frozen importlib._bootstrap_external>", line 399, in _check_name_wrapper
      File "<frozen importlib._bootstrap_external>", line 823, in load_module
      File "<frozen importlib._bootstrap_external>", line 682, in load_module
      File "<frozen importlib._bootstrap>", line 251, in _load_module_shim
      File "<frozen importlib._bootstrap>", line 675, in _load
      File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
      File "<frozen importlib._bootstrap_external>", line 678, in exec_module
      File "<frozen importlib._bootstrap>", line 205, in _call_with_frames_removed
      File "stdlib/3/datetime.pyi", line 12, in <module>
        class tzinfo:
      File "stdlib/3/datetime.pyi", line 13, in tzinfo
        def tzname(self, dt: Optional[datetime]) -> str: ...
    NameError: name 'datetime' is not defined
    

    您可以通过使用 pre-defined 模块对象和全局变量来解决这个问题,然后迭代名称错误直到它导入。我将把它留作 reader 的练习。 Mypy 和其他类型检查器不会尝试执行代码,它们只是构建一个 AST。