每日一包 - inspect

211 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

介绍

inspect模块为Python内置模块,总体来讲该模块可以提供以下几种服务,当然该模块的作用并不仅限于这几种服务,这里只是列出了较为常用的几种,更多的请参考官方文档

  • 类型检查
  • 获取源代码
  • 检查类和函数

使用

getmemebers(obj)

该方法可以获取Python中对象的属性名以及属性值,返回的结果是一个列表,列表中的数据是多个元素的元组。该对象可以是Python中的模块、类、函数、内置类...

import inspect
​
​
class P:
    def __init__(self):
        self.name = "inspect"
        
        
attribute_list = inspect.getmembers(P)
print(attribute_list)

getmodulename(path)

该方法返回path 路径下模块的名称,该方法会根据文件的扩展名判断是否是合法的Python模块,如果如果路径是 .py 结尾的则返回模块名称(文件名), 否则返回None* 。

import inspect
​
a = inspect.getmodulename(r"myfile.csv")
print(a)  # None
​
b = inspect.getmodulename(r"m.py")
print(b)  # m

类型检查相关方法

在这里只简单的介绍几个简单的类型检查方法,在inspect 模块中,涉及到类型检查的方法非常多,有兴趣的XDM可以参考官方文档(docs.python.org/3.9/library…)中的说明

  • ismodule(object) - 判断对象是否是模块,是则返回True,否则返回False
import inspect
​
import m  # 自定义模块,m.py
​
a = inspect.ismodule(m)
print(a)  # True
  • isclass(object) - 判断对象是否是类,是则返回True,否则返回False
import inspect
​
​
class P:
    def __init__(self):
        self.name = "inspect"
​
        
def test():
    """
    :return: int
    """
    return 1
​
​
a = inspect.isclass(test)
print(a)  # False
        
b = inspect.isclass(P)
print(b)  # True
  • ismethod(object) - 判断对象是否是绑定方法(包括类绑定和对象绑定),是则返回True,否则返回False
import inspect
​
class P:
    def __init__(self):
        self.name = "a"
​
    @classmethod
    def test(cls):
        return cls().name
​
    def test1(self):
        return self.name
​
    @staticmethod
    def test2():
        return "name"def test():
    """
    :return: int
    """
    return 1
​
a = inspect.ismethod(P.test)
b = inspect.ismethod(P().test1)
c = inspect.ismethod(P.test2)
d = inspect.ismethod(test)
print(a, b, c, d)  # True True False False
  • isfunction(object) - 判断对象是否是函数,包括lambda 定义的匿名函数,需要注意的是绑定方法会返回False,是则返回True,否则返回False
import inspect
​
class P:
    def __init__(self):
        self.name = "a"
​
    @classmethod
    def test(cls):
        return cls().name
​
    def test1(self):
        return self.name
​
    @staticmethod
    def test2():
        return "name"def test():
    """
    :return: int
    """
    return 1
​
​
a = inspect.isfunction(P.test)
b = inspect.isfunction(P().test1)
c = inspect.isfunction(P.test2)
d = inspect.isfunction(test)
print(a, b, c, d)  # False False True True

检索源代码

  • getdoc(object) - 获取对象的文档字符串,如果没有的话就返回None, 如果对象是类的话,本身有文档就返回自己的文档,如果本身没有文档就会去父类中寻找文档,找到就会返回,找不到返回None
import inspect
​
import m
​
​
class Parent:
    """
    doc: parent
    """
    def __init__(self):
        self.name = "a"
​
​
class P(Parent):
    def __init__(self):
        super().__init__()
​
    @classmethod
    def test(cls):
        return cls().name
​
    def test1(self):
        return self.name
​
    @staticmethod
    def test2():
        return "name"
​
​
def test():
    """
    :return: int
    """
    return 1
​
​
a = inspect.getdoc(P)
b = inspect.getdoc(m)
c = inspect.getdoc(test)
​
print(a)  # doc: parent
print(b)  # doc: I am doc
print(c)  # :return: int
  • getsource(object) - 返回对象的源代码,但是内置的模块、类或者函数会抛出TypeError 错误。
import inspect
import django
​
def test():
    """
    :return: int
    """
    return 1
​
a = inspect.getsource(django)
b = inspect.getsource(test)
c = inspect.getsource(list)  # list是Python内置的类print(a)
print(b)
print(c)  # TypeError: <class 'list'> is a built-in class

signature - 检查类和函数

  • __annotations__ - 查看函数或者方法的签名信息,即参数和返回值的详细信息,包括名称和具体数据类型,返回值是字典。
import inspect
from typing import Tuple
​
​
class Parent:
    """
    doc: parent
    """
    def __init__(self):
        self.name = "a"
​
    def test(self):
        return self.name
​
​
def test(a: int, b: int) -> tuple[int, int]:
    print(a + b)
    return a, b
​
​
print(test.__annotations__)  # {'a': <class 'int'>, 'b': <class 'int'>, 'return': tuple[int, int]}
print(Parent().test.__annotations__)  # {}

我们可以思考一个问题,参数检查势必要在函数执行之前进行,想要在函数执行前增加参数检查的功能势必需要使用装饰器,__annotations__方法返回的值是字典,字典是无序的,而用户按照位置传进来的参数是有序的,如何能够让它们形成对应关系更加方便进行参数检查呢?

此时我们就可以借助inspectsignature 去获取函数签名,得到的返回值是有序字典。

  • signature - 获取函数或者方法的签名信息,将得到的信息存放在有序字典中,从而可以将调用程序是得到的实参与函数签名进行一一对应。同时可以获取每个参数对应的数据类型
import inspect
def test(a: int, b: int) -> tuple[int, int]:
    print(a + b)
    return a, b
​
​
res = inspect.signature(test) 
print(res.parameters) # OrderedDict([('a', <Parameter "a: int">), ('b', <Parameter "b: int">)])
print(res.parameters["a"].annotation)  # <class 'int'> 通过annotation方法获取参数的数据类型

总结

Python是一门动态解释型语言,因此我们无法在变量赋值时就明确表名数据类型,虽然我们可以使用typing模块来进行类型注解,但是这只是提示作用,并没有进行严格限制,这是动态类型语言的特点,并不能说是缺点,因此在一些特定场景下,需要明确严格参数类型或者需要对模块等进行严格限制的话,可以参考使用inspect模块。