Python库介绍(一)——inspect

写完关于sklearn的第一篇文章,一些读者可能遇到了一些不太熟悉的库。我想了想,我有必要在本专栏中穿插一些 Python库的介绍。首先介绍的是inspect库,附上官方文档链接:

我们先看下官方文档的介绍:The inspect module provides several useful functions to help get information about live objects such as modules, classes, methods, functions, tracebacks, frame objects, and code objects. For example, it can help you examine the contents of a class, retrieve the source code of a method, extract and format the argument list for a function, or get all the information you need to display a detailed traceback.

大致翻译一下,inspect是用来获取对象的信息,对象包括模块(往往是一个py文件)、类、方法、函数、报错追踪、帧对象和代码对象。例如,它能用来帮助你检验类的内容,检索一个方法的源代码,提取并格式化函数的参数列表,或者获取用来展示一个traceback的所有信息。

首先,我先大致列举下主要的方法和属性:

基本的方法和属性

1.inspect.getmembers(object[,predicate])

返回由object的成员的(name,value)构成的列表,并且根据name进行排序。如果可选参数predicate是一个判断条件,只有value满足predicate条件的成员才会被返回。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def get_name(self):
        return self.name
    def set_name(self, name):
        self.name = name
    def get_age(self):
        return self.age
    def set_age(self, age):
        self.age = age

返回Person类的所有成员:

if __name__ == "__main__":
    import inspect
    print(inspect.getmembers(Person))

运行结果为:

[('__class__', type),
 ('__delattr__', <slot wrapper '__delattr__' of 'object' objects>),
 ('__dict__',
  mappingproxy({'__module__': '__main__',
                '__init__': <function __main__.Person.__init__(self, name, age)>,
                'get_name': <function __main__.Person.get_name(self)>,
                'set_name': <function __main__.Person.set_name(self, name)>,
                'get_age': <function __main__.Person.get_age(self)>,
                'set_age': <function __main__.Person.set_age(self, age)>,
                '__dict__': <attribute '__dict__' of 'Person' objects>,
                '__weakref__': <attribute '__weakref__' of 'Person' objects>,
                '__doc__': None})),
 ('__dir__', <method '__dir__' of 'object' objects>),
 ('__doc__', None),
 ('__eq__', <slot wrapper '__eq__' of 'object' objects>),
 ('__format__', <method '__format__' of 'object' objects>),
 ('__ge__', <slot wrapper '__ge__' of 'object' objects>),
 ('__getattribute__', <slot wrapper '__getattribute__' of 'object' objects>),
 ('__gt__', <slot wrapper '__gt__' of 'object' objects>),
 ('__hash__', <slot wrapper '__hash__' of 'object' objects>),
 ('__init__', <function __main__.Person.__init__(self, name, age)>),
 ('__init_subclass__', <function Person.__init_subclass__>),
 ('__le__', <slot wrapper '__le__' of 'object' objects>),
 ('__lt__', <slot wrapper '__lt__' of 'object' objects>),
 ('__module__', '__main__'),
 ('__ne__', <slot wrapper '__ne__' of 'object' objects>),
 ('__new__', <function object.__new__(*args, **kwargs)>),
 ('__reduce__', <method '__reduce__' of 'object' objects>),
 ('__reduce_ex__', <method '__reduce_ex__' of 'object' objects>),
 ('__repr__', <slot wrapper '__repr__' of 'object' objects>),
 ('__setattr__', <slot wrapper '__setattr__' of 'object' objects>),
 ('__sizeof__', <method '__sizeof__' of 'object' objects>),
 ('__str__', <slot wrapper '__str__' of 'object' objects>),
 ('__subclasshook__', <function Person.__subclasshook__>),
 ('__weakref__', <attribute '__weakref__' of 'Person' objects>),
 ('get_age', <function __main__.Person.get_age(self)>),
 ('get_name', <function __main__.Person.get_name(self)>),
 ('set_age', <function __main__.Person.set_age(self, age)>),
 ('set_name', <function __main__.Person.set_name(self, name)>)]

返回person实例的所有成员:

person = Person("Bob",25)
print(inspect.getmembers(person))

运行结果为:

[('__class__', <class '__main__.Person'>), ('__delattr__', <method-wrapper '__delattr__' of Person object at 0x000001FB2AA52B88>), ('__dict__', {'name': 'Bob', 'age': 25}), ('__dir__', <built-in method __dir__ of Person object at 0x000001FB2AA52B88>), ('__doc__', None), ('__eq__', <method-wrapper '__eq__' of Person object at 0x000001FB2AA52B88>), ('__format__', <built-in method __format__ of Person object at 0x000001FB2AA52B88>), ('__ge__', <method-wrapper '__ge__' of Person object at 0x000001FB2AA52B88>), ('__getattribute__', <method-wrapper '__getattribute__' of Person object at 0x000001FB2AA52B88>), ('__gt__', <method-wrapper '__gt__' of Person object at 0x000001FB2AA52B88>), ('__hash__', <method-wrapper '__hash__' of Person object at 0x000001FB2AA52B88>), ('__init__', <bound method Person.__init__ of <__main__.Person object at 0x000001FB2AA52B88>>), ('__init_subclass__', <built-in method __init_subclass__ of type object at 0x000001FB2903C508>), ('__le__', <method-wrapper '__le__' of Person object at 0x000001FB2AA52B88>), ('__lt__', <method-wrapper '__lt__' of Person object at 0x000001FB2AA52B88>), ('__module__', '__main__'), ('__ne__', <method-wrapper '__ne__' of Person object at 0x000001FB2AA52B88>), ('__new__', <built-in method __new__ of type object at 0x00007FFE21D67B30>), ('__reduce__', <built-in method __reduce__ of Person object at 0x000001FB2AA52B88>), ('__reduce_ex__', <built-in method __reduce_ex__ of Person object at 0x000001FB2AA52B88>), ('__repr__', <method-wrapper '__repr__' of Person object at 0x000001FB2AA52B88>), ('__setattr__', <method-wrapper '__setattr__' of Person object at 0x000001FB2AA52B88>), ('__sizeof__', <built-in method __sizeof__ of Person object at 0x000001FB2AA52B88>), ('__str__', <method-wrapper '__str__' of Person object at 0x000001FB2AA52B88>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x000001FB2903C508>), ('__weakref__', None), ('age', 25), ('get_age', <bound method Person.get_age of <__main__.Person object at 0x000001FB2AA52B88>>), ('get_name', <bound method Person.get_name of <__main__.Person object at 0x000001FB2AA52B88>>), ('name', 'Bob'), ('set_age', <bound method Person.set_age of <__main__.Person object at 0x000001FB2AA52B88>>), ('set_name', <bound method Person.set_name of <__main__.Person object at 0x000001FB2AA52B88>>)]

现在我们考虑predicate这个参数。根据官方文档的意思,我们可以写一个函数,如果成员的值能够让该函数返回True,则getmembers可以返回该成员,否则则不返回。还是上面的例子,如果我们想返回值为字符串的成员,则可以写如下的代码:

def is_string(x):
    if isinstance(x,str):
        return True
    else:
        return False
print(inspect.getmembers(person,is_string))

运行结果为:

[('__module__', '__main__'), ('name', 'Bob')]


2.inspect.getmodulename(path)

通过输入一个路径返回模块名称。在Python中,一个py文件就是一个module,这需要与包(package)相区别,如果输入的path是一个package,则该方法会返回None。

例如我们返回D:\Software\Anaconda3\Lib\site-packages\sklearn\cluster\_bicluster.py的模块名:

import inspect
path = r"D:\Software\Anaconda3\Lib\site-packages\sklearn\cluster\_bicluster.py"
print(inspect.getmodulename(path))

运行结果为:

'_bicluster'

此时我们将路径变成sklearn文件夹(是package而不是module):

import inspect
path = r"D:\Software\Anaconda3\Lib\site-packages\sklearn"
print(inspect.getmodulename(path))

运行结果为:

None


3.inspect.ismodule(object)

如果object是一个module就返回True,反之则返回False。

4.inspect.isclass(object)

如果object是一个class就返回True,反之则返回False。

5.inspect.ismethod(object)

如果object是一个方法则返回True,反之则返回False。

此外还有一系列的is开头的方法,包括:isfunction, isgeneratorfunction, isgenerator, iscoroutinefunction, iscoroutine, isawaitable, isasyncgenfunction, isasyncgen, istraceback等。

除了is开头的一系列方法外,还有一系列get开头的方法,这里不再赘述,读者可自行查询官方文档。我们将关注点转向inspect的其他对象,包括:

  1. Signatrure object
  2. Parameter
  3. BoundArguments

Signature object

官方文档原文: The Signature object represents the call signature of a callable object and its return annotation. To retrieve a Signature object, use the signature() function.

大致意思就是签名对象代表一个可调用对象的调用签名和返回注解(return annotation)。如果需要创建一个签名对象,可以使用signature()函数。所谓的可调用对象(callable object),一般为函数和方法,当然类也是可调用对象。一个对象是否可调用(callable),可以用内置函数callable来进行判断:

In [1]: import pandas
In [2]: callable
Out[2]: <function callable(obj, /)>
In [3]: callable(pandas.DataFrame)
Out[3]: True
In [4]: def add(x,y):
   ...:     return x+y
In [5]: callable(add)
Out[5]: True

我们回到signature object本身:

from inspect import signature
def foo(a:str,*,b:int,**kwargs):
sig = signature(foo)

(1)

str(sig)

运行结果为:

'(a: str, *, b: int, **kwargs)'

(2)

str(sig.parameters['b'])

运行结果为:

'b:int'

(3)

sig.parameters['b'].annotation

运行结果为:

<class 'int'>

注:Signature object也适用于functools.partial object。

接下来我们考虑Signature object的属性和方法(不带括号的是属性,带括号的是方法):

1.parameters:

返回由键(key)为参数名,值(value)为Parameter对象的有序字典(OrderedDict),该字典的顺序是根据定义函数或方法时的顺序。至于什么是Parameter对象,这是inspect对象中内置的对象,后面会提到。

2.return_annotation:

可调用对象的返回注解。如果可调用对象没有返回,则会返回Signature.empty(inspect中内置的一种object)。

3.bind(*args, **kwargs)

在解读这个方法之前,我们先提及下BoundArguments, 这也是inspect中的一个类,想要了解的读者可以先往下翻看,再回过来看这一部分。


注:所谓的返回注解(return annotation),可以参考以下链接,这里不再赘述: Function Annotations in Python 注:所谓的返回注解(return annotation),可以参考以下链接:

这里我给一个简单的例子:

In [1]: import inspect
In [2]: from typing import Tuple
In [3]: def foo(a: int, *, b: str, **kwargs) -> Tuple[(int, str)]:
   ...:     return a+1, b+'a'
In [4]: sig = inspect.signature(foo)
In [5]: sig.return_annotation
Out[5]: typing.Tuple[int, str]

相信大家都理解了returnannotation是什么意思了。如果没有指定return annotation,那么就会返回insect._empty。

Parameter

我们先看下官方文档的介绍:

class inspect.Parameter (name,kind,*,default=Parameter.empty,annotation=Parameter.empty)

Parameter objects are immutable. Instead of modifying a Parameter object, you can use Parameter.replace() to create a modified copy.

顾名思义,Parameter类是用来包含函数参数(Parameter)的信息的,包括的信息有:

  1. name: 参数名,类型:字符串
  2. kind: 参数类别,类型:枚举值,见下面的介绍
  3. default:默认值,如果没有默认参数则为Parameter.empty
  4. annotation:注解,例如:a: int

注:参数列表中的*代表在创建Parameter实例时,name和kind既可以为关键字参数,也可以是位置参数。而default和annotation必须是关键字参数。

然后提到了Parameter是一个可变对象,创建了一个Parameter实例之后仍然可以修改该实例。

Parameter的属性和方法:

1.empty:就是在参数没有默认值时设置为Parameter.empty。

2.name:参数名。

3.default: 参数的默认值,无默认值时为Parameter.empty。

4.annotation: 参数注解。

5.kind: 参数的种类,以Parameter枚举值的形式展现:

名称 含义
POSITIONAL_ONLY 只能作为位置参数
POSITIONAL_OR_KEYWORD 位置参数或关键字参数
VAR_POSITIONAL *args,传入后会以元组的形式展现
KEYWORD_ONLY 只能作为关键字参数
VAR_KEYWORD **kwargs,传入会以字典的形式展现

BoundArguments

在解读这个类之前,我们先复习一下函数的parameters和arguments的区别:

parameter是指函数定义中的参数,argument是指函数调用时的实际参数。

官方文档提到两点:

  1. BoundArguments holds the mapping of arguments to the function's parameters. 即BoundArguments包含了从arguments到函数的parameters的映射。
  2. BoundArguments is the result of a Signature.bind() or Signature.bind_partial() call. 即BoundArguments是Signature.bind()或者Signature.bind_partial()调用的结果。

上面两点可能比较抽象,那就让我们先创建一个BoundArguments实例,再看究竟吧:

In [6]: def foo(a: int, *,b:str):
   ...:     b = a*2
   ...:     return b
In [7]: import inspect
In [8]: sig = inspect.signature(foo)
In [9]: sig.parameters['a']
Out[9]: <Parameter "a: int">
In [10]: sig.parameters['a'].kind
Out[10]: <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
In [11]: sig.parameters['b'].kind
Out[11]: <_ParameterKind.KEYWORD_ONLY: 3>
In [12]: res = sig.bind(a=1,b='a')

由此,我们创建了一个BoundArgument实例res。可能有读者会注意到参数a是POSITIONAL_OR_KEYWORD,意思就是a可以是位置参数,也可以是关键字参数,而b是 KEYWORD _ONLY, 意思就是a只能是关键字参数。这是由定义foo函数时的*决定的 *后面的参数只能是关键字参数。

接下去,我们以此为例展示BoundArguments的方法和属性:

(1)arguments

In [13]: res.arguments
Out[13]: OrderedDict([('a', 1), ('b', 'a')])

官方文档提到:A mutable mapping of parameters' names to arguments' values. Contains only explicity bound arguments. Changes in arguments will reflect in args and kwargs. 首先我们注意到mutable这个词汇,许多Python的使用者可能会比较熟悉这个词汇,有时候我们会遇到xxx is not a mutate object. 意思就是该对象是不可变的。通常来说,整数、浮点数、字符串、元组属于不可变对象,一旦创建就不可再修改;而列表、字典、集合、numpy数组等属于可变对象。这里提到arguments是一个可变对象,即我们通过对arguments的修改进而修改整个BoundArguments,从而其他属性的值也会跟着变化。

(2)args

In [14]: res.args
Out[14]: (1,)

A tuple of positional arguments values. Dynamically computed from the arguments attribute.

即由位置参数值构成的元组,且可以通过arguments属性动态计算得到,随arguments属性的变化动态变化。

(3)kwargs

In [15]: res.kwargs
Out[15]: {'b': 'a'}

A dict of keyword arguments values. Dynamically computed from the arguments attribute.

即由关键字参数值构成的原则,且可以通过arguments属性动态计算得到,随arguments属性的变化动态变化。

(4)signature

In [16]: res.signature
Out[16]: <Signature (a: int, *, b: str, **kwargs) -> Tuple[int, str]>

(5)apply_defaults()

In [17]: def foo(a,b='ham',*args): pass
In [18]: ba = inspect.signature(foo).bind('spam')
In [19]: ba.arguments
Out[19]: OrderedDict([('a', 'spam')])
In [20]: ba.apply_defaults()
In [21]: ba.arguments
Out[21]: OrderedDict([('a', 'spam'), ('b', 'ham'), ('args', ())])

Set default values for missing arguments.

For variable-positional arguments ( *args ) the default is an empty tuple.

For variable-keyword arguments ( **kwargs ) the default is an empty dict.

将BoundArguments中没有专门设置的argument设置为默认参数。

*args的默认值为空元组。

**kwargs的默认值为空字典。


那么,讲了BoundArguments的方法和属性,BoundArguments有什么用途呢?

事实上,BoundArguments可以用到函数里面。

In [22]: from inspect import signature