python基础:inspect模块各函数的用法

python基础:inspect模块各函数的用法

目录

前言

一、inspect模块总览

1、获取成员与判断

2、获取源代码

3、类与函数

4、调用栈

二、inspect模块方法的使用

1、getmembers

2、getcomments、getdocs

3、signature 获取方法和函数签名

4、类层次体系 getmro()

5、栈与帧 currentframe()、stack()

前言

inspect模块也被称为 检查现场对象。这里的重点在于“现场”二字,也就是当前运行的状态。

inspect模块提供了一些函数来了解现场对象,包括 模块、类、实例、函数和方法。

inspect函数主要用于以下四个方面

对是否是模块、框架、函数进行类型检查 获取源码 获取类或者函数的参数信息 解析堆栈

本文章会先带大家大概了解一下inspect模块有哪些功能,以及有哪些功能函数,然后再结合实例,解释每个方法他们的使用场景以及使用方法。

一、inspect模块总览

1、获取成员与判断

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

第二个参数通常可以根据需要调用如下16个方法;

返回值为object的所有成员,以(name,value)对组成的列表

inspect.ismodule(object): 是否为模块

inspect.isclass(object):是否为类

inspect.ismethod(object):是否为方法(bound method written in python)

inspect.isfunction(object):是否为函数(python function, including lambda expression)

inspect.isgeneratorfunction(object):是否为python生成器函数

inspect.isgenerator(object):是否为生成器

inspect.istraceback(object): 是否为traceback

inspect.isframe(object):是否为frame

inspect.iscode(object):是否为code

inspect.isbuiltin(object):是否为built-in函数或built-in方法

inspect.isroutine(object):是否为用户自定义或者built-in函数或方法

inspect.isabstract(object):是否为抽象基类

inspect.ismethoddescriptor(object):是否为方法标识符

inspect.isdatadescriptor(object):是否为数字标识符,数字标识符有__get__ 和__set__属性; 通常也有__name__和__doc__属性

inspect.isgetsetdescriptor(object):是否为getset descriptor

inspect.ismemberdescriptor(object):是否为member descriptor

2. inspect.getmoduleinfo(path): 返回一个命名元组(name, suffix, mode, module_type)

name:模块名(不包括其所在的package)

suffix:

mode:open()方法的模式,如:'r', 'a'等

module_type: 整数,代表了模块的类型

3. inspect.getmodulename(path):根据path返回模块名(不包括其所在的package)

2、获取源代码

1. inspect.getdoc(object): 获取object的documentation信息

2. inspect.getcomments(object)

3. inspect.getfile(object): 返回对象的文件名

4. inspect.getmodule(object):返回object所属的模块名

5. inspect.getsourcefile(object): 返回object的python源文件名;object不能使built-in的module, class, mothod

6. inspect.getsourcelines(object):返回object的python源文件代码的内容,行号+代码行

7. inspect.getsource(object):以string形式返回object的源代码

3、类与函数

1. inspect.getclasstree(classes[, unique])

2. inspect.getargspec(func)

3. inspect.getargvalues(frame)

4. inspect.formatargspec(args[, varargs, varkw, defaults, formatarg, formatvarargs, formatvarkw, formatvalue, join])

5. inspect.formatargvalues(args[, varargs, varkw, locals, formatarg, formatvarargs, formatvarkw, formatvalue, join])

6. inspect.getmro(cls): 元组形式返回cls类的基类(包括cls类),以method resolution顺序;通常cls类为元素的第一个元素

7. inspect.getcallargs(func[, *args][, **kwds]):将args和kwds参数到绑定到为func的参数名;对bound方法,也绑定第一个参数(通常为self)到相应的实例;返回字典,对应参数名及其值;

4、调用栈

1. inspect.getframeinfo(frame[, context])

2. inspect.getouterframes(frame[, context])

3. inspect.getinnerframes(traceback[, context])

4. inspect.currentframe()

5. inspect.stack([context])

6. inspect.trace([context])

二、inspect模块方法的使用

1、getmembers

inspect.getmembers()函数用于发现对象的成员属性。返回的成员类型取决于我们所传入的对象类型。传入的参数可以是 模块、类、实例、函数、方法。

1.1 语法

inspect.getmembers(object[, predicate])

object可以是 模块、类、实例、函数、方法;

predict表示的是谓语,可以是isclass(),ismodule()等等,用于筛选,看了后面例子就懂了。

1.2 使用场景

用于获取对象的成员属性

1.3 例子

1.3.1 传入参数为module

现在准备了两个py文件,example_inspect.py文件中存放了一些类和方法的定义,作为模块;do_inspect.py文件会将上个py文件当作模块导入;来看这个模块里面有哪些成员。如下

######################### example_inspect.py ##########################

def module_level_function(arg1, arg2, *args, **kwargs):

"""This function is declare in the module"""

local_variable = arg1 * 2

return local_variable

class A(object):

"""The A class"""

def __init__(self, name):

self.name = name

def get_name(self):

"""Return the name of instance"""

return self.name

@classmethod

def test(cls):

pass

@staticmethod

def do_nothing():

pass

instance_of_a = A("simple_instance")

class B(A):

"""This is B class

it is Derived from A"""

# This method is not part of A

def do_something(self):

"""Does some works"""

def get_name(self):

"""Overrides version from A"""

return "B(" + self.name + ")"

######################### do_inspect.py ##########################

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect):

if name.startswith("__"):

continue

print("{} : {!r}".format(name, data))

输出:

# 输出

A :

B :

instance_of_a :

module_level_function :

上面的代码有3个注意点:

1、我们将example_inspect.py作为模块传入getmembers()方法,可以打印出这个模块中所有的成员,并以元组列表形式放回。

2、注意到我在遍历 getmembers()方法时,使用了 if name.startwith("--"),这是为什么呢?因为模块有一些私有属性作为导入实现的一部分,还会包含一组__builtins__。这两个东西的列表非常长,我们一般也都不关心。所以使用if将他们过滤掉。

3、我们还可以将do_inspect.py写法改变一下,如下,利用getmembers的谓语参数,可以选择得到自己想要的成员变量

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect, inspect.isclass): # 修改了这里

if name.startswith("__"):

continue

print("{} : {!r}".format(name, data))

#输出

# A :

# B :

再修改

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect, inspect.isfunction):

if name.startswith("__"):

continue

print("{} : {!r}".format(name, data))

#输出

# module_level_function :

但是,当我们使用 is.method()时,会不会打印出class A、class B内的方法呢?看下面

所以当传入的参数为module时,不能打印出 类的方法。但是可以打印出 模块级别的函数。(与后面参数为 类 时做对比)

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect, inspect.ismethod):

if name.startswith("__"):

continue

print("{} : {!r}".format(name, data))

# 输出为空

1.3.2 传入参数为 类

example_inspect.py不变,修改do_inspect.py文件,如下

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect.A):

print("{} : {!r}".format(name, data))

# 输出的一部分

"""

__reduce_ex__ :

__repr__ :

__setattr__ :

__sizeof__ :

__str__ :

__subclasshook__ :

__weakref__ :

do_nothing :

get_name :

test : >

"""

会打印出一堆结果,这里我只粘贴上了一部分,以双下划线开始的变量基本都是继承object得来的和一些自带的变量。但是注意到,打印出了 方法:get_name()和__init__() do_nothing() test(),如果我们给类A添加了类变量,同样也会打印出来。

当然我们可以使用 谓语 来筛选。

注意: 要查找一个类的成员,使用 iinspect.isfunction();而inspect.ismethod()谓语只识别实例的绑定的方法;

当然,如果类里面定义了类方法,ismethod会打印出这个类方法,因为类也是对象,而类方法就是与类绑定的方法

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect.A, inspect.isfunction):

print("{} : {!r}".format(name, data))

# 输出

"""

__init__ :

do_nothing :

get_name :

"""

#########################################################################

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect.A, inspect.ismethod):

print("{} : {!r}".format(name, data))

# 输出

"""

test : >

"""

继承下的情况

在B继承于A情况下,B拥有了A的方法,因此也被打印出来。

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect.B, inspect.isfunction):

print("{} : {!r}".format(name, data))

# 输出

"""

__init__ :

do_nothing :

do_something :

get_name :

"""

1.3.2 传入参数为 实例

可以看出,类方法和实例方法都被打印出来,而静态方法被忽略掉

import inspect

import example_inspect

for name, data in inspect.getmembers(example_inspect.instance_of_a, inspect.ismethod):

print("{} : {!r}".format(name, data))

# 输出

"""

__init__ : >

get_name : >

test : >

"""

2、getcomments、getdocs

这两个函数在我们编程中用到的很少。这里也就不做介绍了。后面用到的话,直接去官方文档看就可以了。

3、获取源代码:getsource() 和 getsourcelines()

如果可以得到一个模块的 .py文件,则可以使用getsource() 和 getsourcelines()获取类或者方法的原始代码。

多的不说,看代码。

import inspect

import example_inspect

print(inspect.getsource(example_inspect.A))

# 输出

"""

class A(object):

"""The A class"""

def __init__(self, name):

self.name = name

def get_name(self):

"""Return the name of instance"""

return self.name

@classmethod

def test(cls):

pass

@staticmethod

def do_nothing():

pass

"""

####################################################

import inspect

import example_inspect

print(inspect.getsource(example_inspect.A.get_name))

# 输出

"""

def get_name(self):

"""Return the name of instance"""

return self.name

"""

getsourcelines()的返回值是一个tuple,其中包含一个字符串列表,和文件中源代码出现的起始行号,如下

import inspect

import example_inspect

print(inspect.getsourcelines(example_inspect.A.get_name))

# 输出

"""

([' def get_name(self):\n', ' """Return the name of instance"""\n', ' return self.name\n'], 13)

"""

3、signature 获取方法和函数签名

3.1 (补充知识)函数签名是什么?

函数签名对象,表示调用函数的方式,即定义了函数的输入和输出

一个函数由这么bai几部分组成,函数名、参数个数、参数类型、返回值。函数签名就是指 参数个数、参数类型。

那么为什么会有参数签名这么个东西呢?

答:函数在重载时,利用函数签名的不同(即参数个数与类型的不同)来区别调用者到底调用的是那个方法。就如同下面两个方法,语言正是通过他们的函数签名不同,来区别同函数名但是不同参数的函数。

def test(a,b,c):

pass

def test(a,b,c,d):

pass

3.2(补充知识)python函数的参数分类

共有5类,分别是

POSITIONAL_OR_KEYWORD: # 普通参数, python函数的参数多数属于此类VAR_POSITIONAL:*args,定位参数元组VAR_KEYWORD:**kwargs,关键字参数字典KEYWORD_ONLY: 仅限关键字参数, 类似于 下边d=100 的这种POSITIONAL_ONLY(不用管,很难用到):仅限定位参数, python声名的函数不支持, 但是有些c语言实现且不接受关键字参数的函数, 例如: divmod 支持

# 5大类

ParameterKind = [

{"POSITIONAL_OR_KEYWORD": "可以通过定位参数和关键字参数传入的形参, python函数的参数多数属于此类"}, # 普通参数

{"VAR_POSITIONAL": "定位参数元组"}, # *args

{"VAR_KEYWORD": "关键字参数字典"}, # **kwargs

{"KEYWORD_ONLY": "仅限关键字参数"}, # 类似于 下边d=100 的这种

{"POSITIONAL_ONLY": "仅限定位参数, python声名的函数不支持, 但是有些c语言实现且不接受关键字参数的函数, 例如: divmod 支持"},

]

# 前四类

from inspect import signature

def func(a, b, c=10, *args, d=100, **kwargs):

print(a, b, c, args, d, kwargs)

sig_func = signature(func)

for name_, para_ in sig_func.parameters.items():

print(para_.name, para_.kind, para_.default)

"""

参数 参数类型 参数默认值

a POSITIONAL_OR_KEYWORD

b POSITIONAL_OR_KEYWORD

c POSITIONAL_OR_KEYWORD 10

args VAR_POSITIONAL

d KEYWORD_ONLY 100

kwargs VAR_KEYWORD

"""

# 特殊的POSITIONAL_ONLY

sig_divmod = signature(divmod)

for _, _para in sig_divmod.parameters.items(): #

print(_para.name, _para.kind, _para.default)

"""

x POSITIONAL_ONLY

y POSITIONAL_ONLY

"""

3.2 使用inspect.signature获取函数的签名

使用inspect.signature获取函数的签名后,所有参数都以key-value的形式存放在Parameter中。我们继续是使用inspect_example.py的代码作为被检测对象。看下面的代码。

import inspect

import example_inspect

sig = inspect.signature(example_inspect.module_level_function)

print(sig.parameters)

print("\nParameter details:")

for name, param in sig.parameters.items():

if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:

print(" {} (POSITIONAL_OR_KEYWORD)".format(name))

elif param.kind == inspect.Parameter.VAR_POSITIONAL:

print(" {} (VAR_POSITIONAL)".format(name))

elif param.kind == inspect.Parameter.VAR_KEYWORD:

print(" {} (VAR_KEYWORD)".format(name))

elif param.kind == inspect.Parameter.KEYWORD_ONLY:

print(" {} (KEYWORD_ONLY)".format(name))

# 输出

"""

OrderedDict([('arg1', ), ('arg2', ), ('args', ), ('kwargs', )])

Parameter details:

arg1 (POSITIONAL_OR_KEYWORD)

arg2 (POSITIONAL_OR_KEYWORD)

args (VAR_POSITIONAL)

kwargs (VAR_KEYWORD)

"""

3.4使用inspect.signature创建签名

from inspect import Signature, Parameter

# 创建一个函数参数列表,列表内的元素由类Parameter的实例组成

# Parameter实例化时,依次接受参数名、参数类型、默认值和参数注解

# 默认值和参数类型默认为空,这里的空值不是None,而是Parameter.empty,代表没有值

params = [Parameter('x', Parameter.POSITIONAL_OR_KEYWORD),

Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=9),

Parameter('z', Parameter.VAR_KEYWORD)]

# 使用Signature类,接受函数参数列表,实例化出函数签名实例

sig = Signature(params)

print(sig)

这种方式获取的 sig 和我们之前使用 sig = inspect.signature(example_inspect.module_level_function) 语句获取的sig是同一类对象,本质上没有任何区别。

那么,我们把这些参数给扣出来又有什么用呢?

我们可以先为这些参数赋值,然后再去调用我们的函数。就实现了:先给参数赋值,然后再去和函数发生关联、调用。

见3.5

3.5 使用bind() 、bind_partial() 方法给匹配签名

使用函数签名的bind的方法,检查函数参数是否匹配签名。

继续延续 inspect_example.py的例子,通过函数签名的bind方法,接受函数参数。如果匹配,返回参数BoundArguments实例,如果不匹配,则抛出TypeError,并给出详细的异常信息。

通过BoundArguments实例的属性,可以获取函数签名、参数的值等内容。

并且,bind()函数必须指定 必传参数,默认参数不会计入到 BoundArguments中

bind_partial()函数也必须指定必传参数,默认参数不会计入到 BoundArguments中;但是如果使用了apply_defaults()方法,则默认参数也会存入arguments中。

可以认为 没有执行partial.apply_defaults()的bind_partial()函数,和bind()函数没有什么区别

也许你对上面三句话不太懂,那么好好看下面的代码例子,你就懂了。

bind()方法

################### inspect_example.py ###############################

def module_level_function(arg1, arg2="xxx", *args, **kwargs):

"""This function is declare in the module"""

local_variable = arg1 * 2

return local_variable

################### do_inspect.py ##############################

import inspect

import example_inspect

sig = inspect.signature(example_inspect.module_level_function)

bound = sig.bind(

"this is arg1",

"this is arg2"

)

print(bound)

example_inspect.module_level_function(*bound.args, **bound.kwargs)

#输出,可以看到BoundArguments中只有arg1这个参数,而即使arg2有默认参数,也没有将arg2传入BoundArguments

this is arg1this is arg1

xxx

bind_partial()方法

################### inspect_example.py ###############################

def module_level_function(arg1, arg2="xxx", *args, **kwargs):

"""This function is declare in the module"""

local_variable = arg1 * 2

return local_variable

################### do_inspect.py ##############################

import inspect

import example_inspect

sig = inspect.signature(example_inspect.module_level_function)

bound = sig.bind_partial(

"this is arg1",

"this is arg2"

)

print(bound)

example_inspect.module_level_function(*bound.args, **bound.kwargs)

# 输出,可以看出此时bind_partial 和bind 没啥区别

"""

this is arg1this is arg1

xxx

"""

执行了partial.apply_defaults()方法的bind_partial()方法

################### inspect_example.py ###############################

def module_level_function(arg1, arg2="xxx", *args, **kwargs):

"""This function is declare in the module"""

local_variable = arg1 * 2

return local_variable

################### do_inspect.py ##############################

import inspect

import example_inspect

sig = inspect.signature(example_inspect.module_level_function)

bound = sig.bind_partial(

"this is arg1"

)

bound.apply_defaults() # 加了这句

print(bound)

example_inspect.module_level_function(*bound.args, **bound.kwargs)

# 输出,现在所有的默认参数都传入了BoundArguments对象

"""

this is arg1this is arg1

xxx

"""

4、类层次体系 getmro()

这里只介绍getmro()函数,它接受的参数为 类,然后返回值是一个 类的tuple,它会解析出传入类的所有基类,并按照mro的顺序排列。(不从mro的,自行百度吧,比较简单的概念就不说了)

看下面的例子

################### inspect_example.py ###############################

class A(object):

"""The A class"""

def __init__(self, name):

self.name = name

def get_name(self):

"""Return the name of instance"""

return self.name

@classmethod

def test(cls):

pass

@staticmethod

def do_nothing():

pass

class B(A):

"""This is B class

it is Derived from A"""

# This method is not part of A

def do_something(self):

"""Does some works"""

def get_name(self):

"""Overrides version from A"""

return "B(" + self.name + ")"

################### mro_example.py ###############################

import inspect

import example_inspect

class C:

pass

class C_First(C, example_inspect.B):

pass

print(inspect.getmro(C_First))

#执行后,输出

"""

(, , , , )

"""

5、栈与帧 currentframe()、stack()

5.1 currentframe()

为了讲清楚这里,需要补充一些知识:

5.1.1、栈帧是什么,以及程序调用与栈帧

参考这篇链接https://www.cnblogs.com/samo/articles/3092895.html

5.1.2、frame对象

看完了关于栈帧的文章,相信大家已经知道栈帧是什么,以及函数调用时是如何将信息存入栈帧的。

我们经常说的 某一帧 指的就是一个函数存储在栈中的所有信息 就是一帧, 千万不要以为某一帧就是说的某一条数据,帧是一个块的概念。

那么frame对象又是什么呢?

Frame对象表示执行帧,表示程序运行时函数调用栈中的某一帧。

想要获得某个函数相关的栈帧,则必须在调用这个函数且这个函数尚未返回时获取。可以使用sys模块的_getframe()函数、或inspect模块的currentframe()函数获取当前栈帧。

而frame包含了一些属性,其实这些属性对应的就是我们在栈帧里存储的数据,如下

特殊只读属性属性说明f_back前一个堆栈帧(朝向调用者),如果这是底部堆栈帧则为Nonef_code在这个框架中执行的Code对象f_locals用于查找局部变量的字典f_globals用于全局变量f_builtins用于内置名称f_restricted表示该函数是否在限制执行模式下执行的标志f_lasti给出精确的指令(这是代码对象的字节码字符串的索引)特殊可写属性f_trace f_exc_type f_exc_value f_exc_traceback f_lineno当前代码在文件中的哪一行

上面这些参数,大家可以通过ID E调试,看到他们的组成。还有很多参数我没有介绍。我这里只给出一个例子,大家可以自己去调试。

import inspect

import pprint

def recurse(limit, keyword="default", * ,kwonly="must be named"):

local_variable= "."*limit

keyword = "change value of argument"

frame = inspect.currentframe()

print("line {} of {}".format(frame.f_lineno,frame.f_code.co_filename))

pprint.pprint(frame.f_locals)

print("\n")

if limit <= 0:

return

recurse(limit-1)

return local_variable

if __name__ == '__main__':

recurse(2)

#输出

"""

line 8 of C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py

{'frame': ,

'keyword': 'change value of argument',

'kwonly': 'must be named',

'limit': 2,

'local_variable': '..'}

line 8 of C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py

{'frame': ,

'keyword': 'change value of argument',

'kwonly': 'must be named',

'limit': 1,

'local_variable': '.'}

line 8 of C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py

{'frame': ,

'keyword': 'change value of argument',

'kwonly': 'must be named',

'limit': 0,

'local_variable': ''}

"""

5.2 stack()

使用stack()函数,还可以访问到当前帧到第一个调用者的所有栈帧。这个例子与上面的例子相似。只不过它会一直等待,直到递归结束,再打印栈信息。

其实stack()获取的就是frame的列表,是一个按照调用顺序包含了所有栈帧的列表。

那么,它有什么用处呢?

我用它排插过错误,通过定位栈,来定位错误的来源。其他的场景还没有用过。如果后面有的话,再补充。

import inspect

import pprint

def show_stack():

for level in inspect.stack():

print("{}[{}]\n -> {}".format(level.frame.f_code.co_filename,

level.lineno,

level.code_context[level.index].strip()))

pprint.pprint(level.frame.f_locals)

print("\n")

def recurse(limit):

local_variable = "." * limit

if limit <= 0:

show_stack()

return

recurse(limit - 1)

return local_variable

if __name__ == '__main__':

recurse(2)

#输出

"""

C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py[6]

-> for level in inspect.stack():

{'level': FrameInfo(frame=, filename='C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py', lineno=6, function='show_stack', code_context=[' for level in inspect.stack():\n'], index=0)}

C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py[16]

-> show_stack()

{'limit': 0, 'local_variable': ''}

C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py[18]

-> recurse(limit - 1)

{'limit': 1, 'local_variable': '.'}

C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py[18]

-> recurse(limit - 1)

{'limit': 2, 'local_variable': '..'}

C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py[23]

-> recurse(2)

{'__annotations__': {},

'__builtins__': ,

'__cached__': None,

'__doc__': None,

'__file__': 'C:/Users/ASUS/Desktop/项目资料/模型引擎项目/luigi_process_test/code/standard_test/do_inspect.py',

'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001A6CFA71CC0>,

'__name__': '__main__',

'__package__': None,

'__spec__': None,

'inspect': ,

'pprint': ,

'recurse': ,

'show_stack': }

"""

❈ ❈ ❈

相关文章

✧ ✧ ✧
我的前半生贺涵的冰箱是什么牌子?
beat365手机版

我的前半生贺涵的冰箱是什么牌子?

📅 08-02 👁️ 8102
为什么会经常丢办公用笔?
365体育旗下

为什么会经常丢办公用笔?

📅 08-15 👁️ 5306
1牛顿等于多少公克?
365365bet官

1牛顿等于多少公克?

📅 08-24 👁️ 2859