inspect:获取python对象的有用信息

发布时间 2023-05-26 09:32:07作者: tomato-haha

楔子

有些时候,我们需要得到一个对象的某些属性,我们最常用的就是通过type来查看该对象的类型,或者使用dir来查看该对象具有哪些属性。但是python提供了一个非常好的模块:inspect,来帮助我们更好地获取对象的属性,下面就来看看该模块支持哪些方法。

检测对象的种类

这里指的是种类,不是类型。

判断对象是否为模块

关于模块,我们知道python中存在模块和包两个概念,但是其实在底层它们之间并没有区分的那么明显。单独的py文件可以叫一个模块,其实也可以把包看成是一个模块,甚至是空目录也是一个模块,在CPython中,它们都对应PyModuleObject(如果你了解python解释器底层的话)

只不过为了更好地区分,我们把单独的py文件称之为模块,把存放多个py文件的目录称之为包。任何一个py文件都有一个__file__属性,也就是该文件的绝对路径。但是包就不一定了,如果包里面存在__init__.py文件的话,那么这个包的__file__就是内部__init__.py文件的绝对路径,但如果不存在__init__.py文件,那么会报错,提示没有__file__属性,但是在新版本python3.8中,改为不报错,而是返回None。除了__file__,还有一个__path__,任何的包都有__path__,不管内部有没有__init__.py文件,返回的结果是该包的绝对路径,以列表的形式,但是py文件没有__path__

我们具体操作一波

import inspect

import tornado  # tornado是一个目录
from tornado import web  # web是一个py文件
import 一个空目录

# inspect.ismodule可以查看该对象是否为模块
print(inspect.ismodule(tornado))  # True
print(inspect.ismodule(web))  # True
print(inspect.ismodule(一个空目录))  # True

"""
我们看到目录、py文件都是模块,甚至空目录也是一个模块
"""

# 得到该py文件的绝对路径
print(web.__file__)  # C:\python38\lib\site-packages\tornado\web.py

# 该目录内部__init__.py文件的绝对路径
print(tornado.__file__)  # C:\python38\lib\site-packages\tornado\__init__.py

# 但是空目录则没有__file__,因为它内部没有__init__.py文件
# 低版本会报错,比如3.6,但是3.8返回None
print(一个空目录.__file__)  # None


# 任何一个包都有__path__,返回该包的绝对路径,以列表的形式
print(tornado.__path__)  # ['C:\\python38\\lib\\site-packages\\tornado']

# 但是我们看到如果没有__init__.py的话,那么返回的还有些不一样,返回的是一个_NamespacePath
print(一个空目录.__path__)  # _NamespacePath(['D:\\satori\\一个空目录', 'D:\\satori\\一个空目录'])

try:
    # 调用一个py文件的__path__会报错,提示我们没有该属性
    print(web.__path__)
except AttributeError as e:
    print(e)  # module 'tornado.web' has no attribute '__path__'

判断对象是否是一个类

import inspect


print(inspect.isclass(int))  # True
print(inspect.isclass(object))  # True
print(inspect.isclass(type))  # True


class A:
    ...


print(inspect.isclass(A))  # True
print(inspect.isclass(123))  # False

判断对象是否是一个方法

方法可以简单认为是类里面定义的函数,其实更准确的说是用实例获取的函数,不能是通过类获取的。怎么理解呢?都说实例在调用类里面函数的时候会自动将实例本身传给self,那么为什么会自动传递呢?其实python中的函数在CPython中对应的是PyFunctionObject,如果是方法的话,那么会对PyFunctionObject进行封装,得到PyMethodObject。PyMethodObject里面有一个im_self属性,就是python中的self,如果是实例调用,那么底层会自动将im_self(该实例对象)作为第一个参数传进去。至于实例调用的时候为什么会自动传递,则是通过描述符的方式。是的,你没有看错,python底层使用的是描述符,有兴趣可以自己去研究一下。所以类去获取得到的就是普通的函数,实例获取得到的才叫方法,因为实例在获取的时候会将函数包装成方法。

import inspect


class A:

    def foo(self):
        pass


# 我们说实例去调用才叫做方法,类调用的不算
print(inspect.ismethod(A.foo))  # False
print(inspect.ismethod(A().foo))  # True

判断对象是否是描述符

描述符有两种,分别是非数据描述符和数据描述符。

  • 非数据描述符:内部实现了__get__方法,但是没有实现__set__方法
  • 数据描述符:内部实现了__set__方法或者实现了__delete__方法
import inspect


class A:

    def __get__(self, instance, owner):
        pass


class B:

    def __get__(self, instance, owner):
        pass

    def __set__(self, instance, value):
        pass


# 判断是否是非数据描述符,我不知道名字为什么叫ismethoddescriptor
# 当然传入的要是类的实例对象,至于类、函数、方法则肯定不是描述符
print(inspect.ismethoddescriptor(A()))  # True
print(inspect.ismethoddescriptor(B()))  # False

# A内部实现了__get__,所以A()是非数据描述符
# B内部实现了__set__,所以B()是数据描述符


# 判断是否是数据描述符,这个名字就比较形象了
print(inspect.isdatadescriptor(A()))  # False
print(inspect.isdatadescriptor(B()))  # True

判断对象是否是函数

import inspect


def foo():
    pass


class A:
    def foo(self):
        pass


print(inspect.isfunction(foo))  # True
print(inspect.isfunction(A.foo))  # True
print(inspect.isfunction(A().foo))  # False

"""
我们说类获取才是函数,实例获取会包装成方法
"""

# 但如果它们被装饰器装饰了呢?
class B:

    @property
    def f1(self):
        pass

    @staticmethod
    def f2():
        pass

    @classmethod
    def f3(cls):
        pass


print(inspect.isfunction(B.f1))  # False
print(inspect.isfunction(B().f1))  # False
"""
我们看到被property装饰之后,无论谁去调用,都不是函数了
很好理解,因为B.f1得到的就是一个property对象
而B().f1直接拿到了返回值,这里是一个None,当然也不是函数。
当然如果你返回的就是一个函数,那么inspect.isfunction(B().f1)也是正确的,不过此时判断的就不再是f1了,而是f1的返回值
"""

print(inspect.isfunction(B.f2))  # True
print(inspect.isfunction(B().f2))  # True
"""
被staticmethod装饰之后,如果你自己手动实现过staticmethod的话
那么你会发现就等同于没有被staticmethod装饰的B.f2
因为它不需要自动传递参数,不需要包装成方法,所以是一个函数,无论是类获取还是实例获取
"""

print(inspect.isfunction(B.f3))  # False
print(inspect.isfunction(B().f3))  # False
"""
但是我们看到被classmethod装饰之后,就不再是函数了
没错,因为此时类去调用的时候会自动将自身传递给参数cls,那么这和实例调用传递self是一个道理
只不过类传递给cls这是我们自己通过classmethod实现的,而实例传递给self是python底层自动实现的,但是它们的本质都是一样的
都被包装成了方法,此时无论是类获取还是实例获取,得到的都是方法,并且调用的时候第一个参数cls都是类本身
"""
# 显然ismethod返回的结果是正确的,因为它们是方法
print(inspect.ismethod(B.f3))  # True
print(inspect.ismethod(B().f3))  # True

判断对象是否是生成器

import inspect

x = (_ for _ in [1, 2, 3])


def foo():
    yield 123


print(inspect.isgenerator(x))  # True
print(inspect.isgenerator(foo))  # False
print(inspect.isgenerator(foo()))  # True

"""
foo是一个生成器函数,它不是生成器,它无法调用__next__产出值
只有在调用foo()的时候,返回的才是一个生成器
"""

判断对象是否是生成器函数

import inspect

x = (_ for _ in [1, 2, 3])


def foo():
    yield 123


print(inspect.isgeneratorfunction(x))  # False
print(inspect.isgeneratorfunction(foo))  # True
print(inspect.isgeneratorfunction(foo()))  # False

判断对象是否为协程

import inspect


async def foo():
    pass


# 使用def定义的是函数,使用async def定义的是协程函数
# 协程函数调用之后得到的就是一个协程
print(inspect.iscoroutine(foo()))  # True

# 另外如果async def定义的协程函数里面出现了yield,那么就不叫协程函数了,而叫做异步生成器函数
# 我们后面会说

判断对象是否为协程函数

import inspect


async def foo():
    pass


print(inspect.iscoroutinefunction(foo))  # True
print(inspect.iscoroutinefunction(foo()))  # False

判断对象是否为异步生成器

import inspect


# 异步生成器,首先是一个生成器,而且还要是异步的
# 这个异步就体现在需要是使用async定义的协程函数
# 当然组合起来就不是生成器、也不是协程函数,而是异步生成器函数了,进行调用会得到异步生成器
async def foo():
    yield 123
    yield 456
    yield 789

# 判断是否是异步生成器
print(inspect.isasyncgen(foo()))  # True

# 多提一句,那我要如何获取里面的值呢?
# 显然对于异步生成器没有__next__方法,但是它有__anext__,这里的a指的就是async
# 但是这样获取不到值
print(foo().__anext__())  # <async_generator_asend object at 0x000002457F015FC0>

# 其实对于协程或者异步生成器来讲
# 如果想要运行,必须要扔到事件循环里面去
# 而运行协程则需要使用官方提供了asyncio这个库,当然tornado也是可以的,因为tornado5.0之后底层的事件循环使用的就是asyncio
# 当然如果想运行一个async def定义的协程函数或者异步生成器,必须也要在async def里面定义的协程里面运行
async def main1():
    f = foo()
    # 另外对于协程或异步生成器来说,无论是yield还是return,我们必须要使用await关键字才能获取值
    # 而await关键字只能出现在async def定义的协程函数中
    print(await f.__anext__())
    print(await f.__anext__())
    print(await f.__anext__())


async def main2():
    f = foo()
    # 当然还有更加pythonic的方法,就是使用async for
    # 同理,异步的上下文管理则是async with
    async for _ in f:
        print(_)


if __name__ == '__main__':
    import asyncio
    asyncio.run(main1())
    """
    123
    456
    789
    """
    asyncio.run(main2())
    """
    123
    456
    789
    """
"""
关于python中的协程,个人觉得学习起来还是有些费劲的
尤其是早期python没有协程,是通过yield和yield from来进行模拟的
如果把协程和yield混合起来使用,确实让人感到困惑。
但并不是说yield不重要,它非常重要,理解yield和yield from能让你更快速理解python中的协程(async 和 await)

只是希望不要把async和yield一起混用,如果是yield的话,那么不要让它出现在async def定义的协程中,就把它当成是普通的生成器来使用即可
"""
# 另外python中的协程是一个稍微复杂的概念,以及asyncio的使用,这里不可能全部讲清楚
# 有兴趣的话可以参考我的这一篇博客:https://www.cnblogs.com/traditional/p/11828780.html

判断对象是否是异步生成器函数

import inspect


async def foo():
    yield 123
    yield 456
    yield 789


print(inspect.isasyncgenfunction(foo))  # True
print(inspect.isasyncgenfunction(foo()))  # False

另外关于协程、协程函数,生成器、生层器函数等等,到底带不带函数二字,其实我们也不会区分的这么明显。比如async def是定义一个协程函数,但是我们平常都会说定义一个协程,所以心里面清楚就行。

判断对象是否可awaitable

如果不了解python中的协程的话,那么这个可能有些难理解。可awaitable,其实主要体现在该对象是否可以使用await关键字。

import inspect


async def foo():
    return 123


# 我们说一个协程都是可awaitable的
print(inspect.isawaitable(foo()))  # True


async def bar():
    yield 123


# 但是异步生成器则不行
print(inspect.isawaitable(bar()))  # False
# 而异步生成器在使用__anext__获取值的时候是可awaitable的
print(inspect.isawaitable(bar().__anext__()))  # True


class A:

    def __await__(self):
        return 123


# 如果我们自定义的类实现了__await__魔法方法的话,那么这个类的实例对象则也是可awaitable的
print(inspect.isawaitable(A()))  # True

判断对象是否是一个traceback

这个traceback是发生错误的时候产生的回溯栈。

import inspect
import sys

try:
    1 / 0
except ZeroDivisionError:
    _, _, tb = sys.exc_info()
    print(inspect.istraceback(tb))  # True

判断对象是否是一个栈帧

关于python中的栈帧,是一个比较复杂的话题,如果扯得话,又能扯很远。所以干脆不扯了,可以看我的其它博客,专门介绍python解释器的。

import inspect


frame = None


def foo():
    global frame
    # 使用inspect.currentframe()可以获取当前函数的栈帧
    frame = inspect.currentframe()


# 此时为None,所以不是
print(inspect.isframe(frame))  # False
# 当执行完函数之后
foo()
print(inspect.isframe(frame))  # True

判断对象是否是字节码对象

字节码对象就是python编译之后的结果,就是pyc文件里面存储的内容。

import inspect


def foo():
    pass


# 调用函数的__code__方法,可以拿到函数的字节码
# 同理对于生成器、协程、异步生成器来说也是一样的
print(inspect.iscode(foo.__code__))  # True

判断对象是否是内置函数或者方法

import inspect


# 要么是builtin里面的函数,要么是里面的类创建的实例对象的某个方法
print(inspect.isbuiltin(globals))  # True
print(inspect.isbuiltin((1).bit_length))  # True
print(inspect.isbuiltin(__name__))  # False

判断对象是否是抽象类

如果是抽象类,那么这个类的元类要是abc.ABCMeta,并且内部要有一个抽象方法,也就是要被abc.abstractmethod装饰的方法

import inspect
import abc


class A(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def foo(self):
        pass


print(inspect.isabstract(A))  # True

# 但是ABCMeta本身不是抽象类
print(inspect.isabstract(abc.ABCMeta))  # False

获取对象的信息

获取对象的所有属性名和属性值

我们之前获取对象的属性是通过dir的方式,但是dir返回的是属性的名字,而通过inspect可以同时获取属性名和值。

import inspect
from pprint import pprint

# 返回的是一个列表,里面是多个属性名和属性值组成的二元tuple
pprint(inspect.getmembers(1))
"""
[('__abs__', <method-wrapper '__abs__' of int object at 0x00007FFCAC20C6A0>),
 ('__add__', <method-wrapper '__add__' of int object at 0x00007FFCAC20C6A0>),
 ('__and__', <method-wrapper '__and__' of int object at 0x00007FFCAC20C6A0>),
 ('__bool__', <method-wrapper '__bool__' of int object at 0x00007FFCAC20C6A0>),
 ('__ceil__', <built-in method __ceil__ of int object at 0x00007FFCAC20C6A0>),
 ('__class__', <class 'int'>),
 ...
 ...
]
"""

# 其实如果把里面元组的第一个元素取出来的话,会发现和dir返回的结果是一样的
# 转成集合,忽略掉顺序
print(set([_[0] for _ in inspect.getmembers(1)]) == set(dir(1)))  # True

for _ in inspect.getmembers(10):
    if _[0] == "__add__":
        print(_[1](20))  # 30

获取一个类的相关信息

import inspect
from pprint import pprint

class A: pass

# 返回的是多个Attribute对象组成的列表,注意:必须要传递一个类才可以
pprint(inspect.classify_class_attrs(A))
"""
[Attribute(name='__class__', kind='data', defining_class=<class 'object'>, object=<class 'type'>),
 Attribute(name='__delattr__', kind='method', defining_class=<class 'object'>, object=<slot wrapper '__delattr__' of 'object' objects>),
 Attribute(name='__dict__', kind='data', defining_class=<class '__main__.A'>, object=<attribute '__dict__' of 'A' objects>),
 Attribute(name='__dir__', kind='method', defining_class=<class 'object'>, object=<method '__dir__' of 'object' objects>),
 Attribute(name='__doc__', kind='data', defining_class=<class '__main__.A'>, object=None),
 Attribute(name='__eq__', kind='method', defining_class=<class 'object'>, object=<slot wrapper '__eq__' of 'object' objects>),
 Attribute(name='__format__', kind='method', defining_class=<class 'object'>, object=<method '__format__' of 'object' objects>),
 Attribute(name='__ge__', kind='method', defining_class=<class 'object'>, object=<slot wrapper '__ge__' of 'object' objects>),
 Attribute(name='__getattribute__', kind='method', defining_class=<class 'object'>, object=<slot wrapper '__getattribute__' of 'object' objects>),
 ...
 ...
]
"""
# 我们看一些这个Attribute,里面有
# name: 内部的属性名
# kind:种类,如果是一个函数,那么是method,如果是属性,那么是data,同理还有class method和static method
# defining_class:这个属性或者函数是属于谁的,比如__dict__,A这个类自身存在,那么就是<class '__main__.A'>,但__dir__的话,A没有,所以这个方法是object提供的
# object:类调用相应属性或者函数返回的结果

# 我们以__class__和__dir__为例
print(A.__class__)  # <class 'type'>
print(A.__dir__)  # <method '__dir__' of 'object' objects>
# 怎么样返回的结果和上面的是不是一样的呢?


for _ in inspect.classify_class_attrs(A):
    # 具体属性直接通过.来调用即可
    if _.name == "__str__":
        # 由于_.object返回的都是类调用的函数,那么需要手动传递self
        # 因为是object的__str__方法,那么我们传递任何东西都可以
        print(_.object(A()))  # <__main__.A object at 0x000001B15A571580>
        print(int)  # <class 'int'>
        print([{()}])  # [{()}]

获取一个类的mro

import inspect


class A(int): ...


print(inspect.getmro(A))  # (<class '__main__.A'>, <class 'int'>, <class 'object'>)
print(A.__mro__)  # (<class '__main__.A'>, <class 'int'>, <class 'object'>)

对被装饰器装饰的函数进行还原

import inspect
from functools import wraps


def deco(cls):
    @wraps(cls)
    def inner():
        return "inner"
    return inner


@deco
def func():
    return "func"


# 我们看到func被deco装饰了,那么此时的func指向了deco函数内部的inner
print(func())  # inner
# 那么我们可以通过inspect.unwrap函数进行还原,得到原本的函数
print(inspect.unwrap(func)())  # func

# 但是注意:只有装饰器内层函数加上了@wraps(cls)才可以还原
# 因为如果不加的话,那么函数的整个信息就变了,unwrap还原是需要原来函数的元信息的
# 即使是多个装饰器也是可以的,但内层函数都要有@wraps(cls)
# 如果多个装饰器出现了没有被@wraps(cls),那么就会停止。至于顺序是从下往上还是从上往下可以自己去研究一下

除此之外unwraps还接收一个stop参数,必须通过关键字传递。该参数需要传递一个接收一个参数的函数,然后在去掉装饰器的时候会将结果传到这个函数里面,如果函数返回True,那么提前终止。如果返回False,那么unwrap会一直解包,直到返回装饰链中的最后一个函数,也就是最原始的函数,有兴趣可以自己试一下。

获取一个字符串的缩进长度

import inspect


s1 = " aaa"
s2 = "    "
s3 = ""
print(inspect.indentsize(s1))  # 1
print(inspect.indentsize(s2))  # 4
print(inspect.indentsize(s3))  # 0

# 返回的实际上就是开头空格的长度
print(len(s1) - len(s1.lstrip()))  # 1
print(len(s2) - len(s2.lstrip()))  # 4
print(len(s3) - len(s3.lstrip()))  # 0
# inspect.indentsize内部也是这么做的
# 个人觉得这个可以自己实现

获取对象的文档注释

import inspect


class A:
    """
    这是类A
    """


def b():
    """
    这是函数B
  @return:
    """


print(inspect.getdoc(A))  # 这是类A
print(inspect.getdoc(b))
"""
  这是函数B
@return:
"""
# 可以看到inspect.getdoc没有把多余的空格算进去

print(A.__doc__)
"""

    这是类A
    
"""
print(b.__doc__)
"""

    这是函数B
  @return:
    
"""
# 但是对象的__doc__属性就是相当于文档字符串原原本本的输出出来

清除文档的缩进

import inspect


class A:
    """
    这是类A
    """


def b():
    """
    这是函数B
  @return:
    """

print(A.__doc__)
"""

    这是类A
    
"""
print(inspect.cleandoc(A.__doc__))  # 这是类A

# 可以看到这个和刚才的getdoc是一样的
# 但是它不仅仅是针对doc,普通的字符串也是可以的

s = """

    xxx
  xxx
  
"""
print(inspect.cleandoc(s))
"""
  xxx
xxx
"""
# 关于去除缩进,个人更推荐textwrap这个模块,在我的博客<<python常用模块>>里面有,可以翻一下。

查看一个对象是被定义在哪个文件里的

import inspect

from tornado.ioloop import IOLoop
import tornado


print(inspect.getfile(tornado))  # C:\python38\lib\site-packages\tornado\__init__.py
print(inspect.getfile(IOLoop))  # C:\python38\lib\site-packages\tornado\ioloop.py

根据文件路径返回模块名

import inspect

print(inspect.getmodulename(r"C:\xxxx\iolaaaoop.py"))  # iolaaaoop
print(inspect.getmodulename(r"C:\xxxx\iolaaaoop.pyc"))  # iolaaaoop
print(inspect.getmodulename(r"C:\xxxx\iolaaaoop.pyd"))  # iolaaaoop

# 所以不管这个文件是否存在,只要是以py、pyc、pyd结尾的
# 那么返回文件名,也就是你用来import的部分

查看一个对象是被定义在哪个文件里的

import inspect

from tornado.ioloop import IOLoop
import tornado


print(inspect.getsourcefile(tornado))  # C:\python38\lib\site-packages\tornado\__init__.py
print(inspect.getsourcefile(IOLoop))  # C:\python38\lib\site-packages\tornado\ioloop.py

# 和前面介绍inspect.getfile比较类似
# 或者使用inspect.getabsfile
print(inspect.getabsfile(tornado))  # C:\python38\lib\site-packages\tornado\__init__.py
print(inspect.getabsfile(IOLoop))  # C:\python38\lib\site-packages\tornado\ioloop.py

根据对象返回对象所在的模块

import inspect

from tornado.ioloop import IOLoop
import tornado


print(inspect.getmodule(tornado))  # <module 'tornado' from 'C:\\python38\\lib\\site-packages\\tornado\\__init__.py'>
print(inspect.getmodule(IOLoop))  # <module 'tornado.ioloop' from 'C:\\python38\\lib\\site-packages\\tornado\\ioloop.py'>

print(inspect.getmodule(tornado).version)  # 6.0.3
print(tornado.version)  # 6.0.3


# 我们知道还可以通过__module__来查看,但是它们只能针对类、类的实例和函数来用
# 模块和包没有,但是getmodule可以返回,当然返回的就是其本身

获取参数信息

获取一个函数的所有参数信息

import inspect


def foo(
        a: int,
        b: str,
        c: int = 1,
        *args: str,
        d: int = 1,
        **kwargs: dict
) -> None:
    pass


# 接收一个函数
print(inspect.getfullargspec(foo))
"""
FullArgSpec(
    args=['a', 'b', 'c'], 
    varargs='args', 
    varkw='kwargs', 
    defaults=(1,), 
    kwonlyargs=['d'], 
    kwonlydefaults={'d': 1}, 
    annotations={'return': None, 'a': <class 'int'>, 
                 'b': <class 'str'>, 'c': <class 'int'>, 
                 'args': <class 'str'>, 'd': <class 'int'>, 
                 'kwargs': <class 'dict'>})
"""
# 返回的是一个namedtuple,里面属性如下
# args:即可以通过位置参数传递、也可以通过关键字参数传递的 所有参数名
# varargs:通过扩展位置参数传递的参数名,也就是*xxx
# varkw:通过扩展关键字参数传递的参数名,也就是**xxx
# defaults:所有参数的默认值,这里的参数当然是args里面的参数对应的默认值
# kwonlyargs:只能通过关键字参数传递的参数名,我们注意到d是在*args后面,那么如果d不通过关键字传递,那么将永远无法给d传参,因为位置参数永远会被*args接收
# kwonlydefaults:只能通过关键字参数传递的参数的默认值,是一个字典
# annotations:注解,这是在python3.5增加的。其它的不用说,关键来看*args和**kwargs
               # 我们上面的注解表示,传递的扩展位置参数都必须是str类型,传递的扩展关键字参数都必须是dict类型
    
# 即使对于类也是一样的,会自动获取内部__init__函数的参数信息    

获取一个函数的所有参数信息

import inspect


def foo(
        a: int,
        b: str,
        c: int = 1,
        *args: str,
        d: int = 1,
        **kwargs: dict
) -> None:
    pass

# 接收一个函数,返回一个Signature对象
print(inspect.signature(foo))  # (a: int, b: str, c: int = 1, *args: str, d: int = 1, **kwargs: dict) -> None
print(type(inspect.signature(foo)))  # <class 'inspect.Signature'>

s = inspect.signature(foo)
# 返回所有参数
print(s.parameters)
# 得到的是一个OrderedDict,里面的value是Parameter类型
"""
OrderedDict(
[('a', <Parameter "a: int">), ('b', <Parameter "b: str">), 
('c', <Parameter "c: int = 1">), ('args', <Parameter "*args: str">), 
('d', <Parameter "d: int = 1">), ('kwargs', <Parameter "**kwargs: dict">)]
)
"""
# Parameter有如下属性
print(s.parameters["a"].name, s.parameters["c"].name)  # a c
print(s.parameters["a"].default, s.parameters["c"].default)  # <class 'inspect._empty'> 1
print(s.parameters["a"].annotation, s.parameters["c"].annotation)  # <class 'int'> <class 'int'>
print(s.parameters["a"].kind, s.parameters["c"].kind)  # POSITIONAL_OR_KEYWORD POSITIONAL_OR_KEYWORD

结束

还有一部分方法个人觉得不常用,所以就不说了,因为涉及到栈帧,而且如果你熟悉栈帧的话,那么很多功能你可以自己实现,没必要使用里面的。就比如根据对象获取该对象所在的文件路径,这个自己就可以实现的。但是里面很多方法还是很有用的,至于具体什么时候使用就由你自己决定