Python查看对象或者方法使用帮助的三板斧

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python查看对象或者方法使用帮助的三板斧相关的知识,希望对你有一定的参考价值。

参考技术A

python中每一个对象或者对象的方法都有可以使用三种方式查看相关的使用方法和帮助文档。

直接调用对象或者方法的 doc 属性,或者使用dir()或者使用help()来查看就可以了。

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:\\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

结束

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

以上是关于Python查看对象或者方法使用帮助的三板斧的主要内容,如果未能解决你的问题,请参考以下文章

10. Python 帮助

Wireshark分析艺术【读书总结】

Python查看对象属性的方法

(数据分析三板斧)第一斧Numpy-第一节:Numpy基本了解

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

Python怎么查看帮助信息