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


    楔子

    有些时候,我们需要得到一个对象的某些属性,我们最常用的就是通过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:python38libsite-packages	ornadoweb.py
    
    # 该目录内部__init__.py文件的绝对路径
    print(tornado.__file__)  # C:python38libsite-packages	ornado\__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:python38libsite-packages	ornado\__init__.py
    print(inspect.getfile(IOLoop))  # C:python38libsite-packages	ornadoioloop.py
    

    根据文件路径返回模块名

    import inspect
    
    print(inspect.getmodulename(r"C:xxxxiolaaaoop.py"))  # iolaaaoop
    print(inspect.getmodulename(r"C:xxxxiolaaaoop.pyc"))  # iolaaaoop
    print(inspect.getmodulename(r"C:xxxxiolaaaoop.pyd"))  # iolaaaoop
    
    # 所以不管这个文件是否存在,只要是以py、pyc、pyd结尾的
    # 那么返回文件名,也就是你用来import的部分
    

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

    import inspect
    
    from tornado.ioloop import IOLoop
    import tornado
    
    
    print(inspect.getsourcefile(tornado))  # C:python38libsite-packages	ornado\__init__.py
    print(inspect.getsourcefile(IOLoop))  # C:python38libsite-packages	ornadoioloop.py
    
    # 和前面介绍inspect.getfile比较类似
    # 或者使用inspect.getabsfile
    print(inspect.getabsfile(tornado))  # C:python38libsite-packages	ornado\__init__.py
    print(inspect.getabsfile(IOLoop))  # C:python38libsite-packages	ornadoioloop.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
    
    

    结束

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

  • 相关阅读:
    linux 安装 svn
    人群计数:Single-Image Crowd Counting via Multi-Column Convolutional Neural Network
    Ubuntu查看CPU占用和使用情况
    看完这篇文章,我奶奶都懂了HTTPS原理
    EfficientNet算法笔记
    Soft NMS算法笔记
    DenseNet算法详解
    AI竞赛服务平台—— FlyAI
    Cornernet训练自己的数据
    深度学习物体检测:CornerNet
  • 原文地址:https://www.cnblogs.com/traditional/p/12404871.html
Copyright © 2020-2023  润新知