前言: 目的是为了实现一个可以获取python 中类的属性、方法、以及类或方法描述的工具类,在这个基础上还要实现一些类的基本限制,比如首字母大写,比如类或方法描述的编写(很多人编程的时候最生气的两件事,别人不写注释,让你写注释,为了避免此类事情发生,所以进行了校验限制 手动狗头)
Python作为一个自由度极高的编程语言. 元类 即type这个基类有着不可磨灭的功劳
简单介绍一下type 很多人对于type是不陌生的因为通过type(args)是可以打印出这个参数的类型
但是作为基类 显然type是和object一样可以被指定继承的 也就是class T(type):
而python中区分元类和普通类的方式 就是显示的指定type还是隐式或显示的指定object这个类
如何实现一个元类?
首先在定义类的时候 我们需要指定为type
class MyClass(type):
def __init__(cls, class_name, class_base, class_dic):
print(cls)# 谁指定继承MyClass这个元类 cls便是谁
print(class_name) # 继承MyClass元类的自定义类名
print(class_base) # 空元组
print(class_dic)# 这个类包含了哪些属性方法以及其他属性 都可以在这里看到
super(MyClass, cls).__init__(class_name, class_base, class_dic)
class
接下来我们创建两个自定义的类 来继承指定的元类 并执行一下 看看4个参数分别打印了什么
# 继承指定的元类只需要在定义类的时候通过参数metaclass来指定元类的名称即可
class P(metaclass=MyClass):
"""
hi p
"""
data = []
def __init__(self, a, b):
self.a = a
self.b = b
def pr(self, name: str = "张三") -> int:
"""
这里是注释
:return:
"""
# print(self.a, self.b)
return self.a + self.b
class T1(metaclass=MyClass):
"""
这是T1的注释
"""
def __init__(self, name, age):
self.name = name
self.age = age
def clr(self) -> tuple:
"""
clr方法的注释
:return:
"""
return self.name, self.age
P(1, 2).pr(name="张三")
T1("zz", 15).clr()
通过打印的参数 我们不难看出 cls指向了实例化后的对象 claa_name是实例化对象的类名,class_base是元组类型,class_dic是类所拥有的属性及方法等
那么通过这些属性的作用: 我们便可以去实现一些自定义校验的工作以及api文档所需要的一些数据
完整代码如下:
import pprint # 格式化打印的库
import sys # 运行时
__document__ = [] # 用于存放文档
# 这里是用来拦截help()打印的方法注释 并将注释进行一些处理 元类中无法获取到具体方法内的入参 注释 以及响应值.
class TextArea:
# 初始化.
def __init__(self):
self.buffer = []
# 实现write这个方法 后续赋值给stdout时会自动调用将标注输出流收集至列表中
def write(self, *args):
self.buffer.append(args)
# 用于解析方法中的数据
def params_buffer(self, explanatory: str)->dict:
# 首先将方法中列表中切出来
func = self.buffer[0][0].split("\n\n")[1]
# 获取参数 返回的args[0]是方法名, 1 是入参
args = func.split("(")
# 解析出参数以及返回值
params = str(args[1]).replace(")", "").split("->")
# 将self这个指针属性删掉
methods_params = params[0].replace('self,', '')
p = ""
# 获取返回类型并做处理
return_params = params[1:]
if len(return_params) > 0:
p = return_params[0].split('\n')[0].strip()
# 整合接口文档数据并返回
return {
"method": args[0], # 方法名
"params": methods_params.replace("\n", ""), # 参数
"return_value": p, # 返回值
"func_explanatory": explanatory, # 注释
}
# 通过指定type来定义元类
class MyClass(type):
def __init__(cls, class_name, class_base, class_dic):
**# 通过在class_dic参数中取出来__doc__来确认自定义类是否进行了注释 如果没有则抛出异常
if class_dic.get("__doc__") is None or len(class_dic.get("__doc__").strip()) == 0:
raise TypeError(f"在{class_name}类中请添加文档注释")
**# 这里可以做类的命名规则校验 这里只是为了提现该参数的作用 所以做了命名规则校验**
if not class_name.istitle():
raise TypeError(f"{class_name}命名请以大写字符开头")
methods_list = []
# 遍历class_dic拿出所有的值
for k, v in class_dic.items():
# 获取类中的方法
if "function" in str(type(v)):
# 通过help()方法将所有方法详细信息打印出来 用于获取
text = sys.stdout
sys.stdout = TextArea()
help(v)
text_area, sys.stdout = sys.stdout, text
# 调用方法, 并将注释信息解析出来
api = text_area.params_buffer(str(v.__doc__)
.replace("\n ", "")
.replace(" ", "")
.replace(":return:", ""))
# 这里做了一个方法描述的校验 所有不是__init__的方法都需要编写方法描述 否则将抛出异常
if api.get("func_explanatory") == 'None' and api.get("method") != "__init__":
raise TypeError(f"在类{cls.__name__}中,方法:{api.get('method')}未声明注释")
methods_list.append(api)
# 获取所有属性的名称以及其类型
attribute = [{
d: str(type(getattr(cls, d)))
.replace(", "")
.replace(">", "")
.replace("'", "").strip()
} for d in dir(cls) if "__" not in str(d)]
# 将所有的数据添加到全局变量中, 用于打印所有的文档
__document__.append({
"className": cls.__name__,
"class_explanatory": str(class_dic.get("__doc__")).replace("\n", ""),
"methods": methods_list,
"attribute": attribute,
})
# 自定义元类最终需要通过super这个方法调用父类的能力来进行创建类
super(MyClass, cls).__init__(class_name, class_base, class_dic)
# 定义的类 用来继承MyClass自定义元类
class P(metaclass=MyClass):
"""
hi p
"""
data = []
def __init__(self, a, b):
self.a = a
self.b = b
def pr(self, name: str = "张三") -> int:
"""
这里是注释
:return:
"""
# print(self.a, self.b)
return self.a + self.b
class T1(metaclass=MyClass):
"""
这是T1的注释
"""
def __init__(self, name, age):
self.name = name
self.age = age
def clr(self) -> tuple:
"""
clr方法的注释
:return:
"""
return self.name, self.age
P(1, 2).pr(name="张三")
T1("zz", 15).clr()
pprint.pprint(__document__)
最终:
最后我们来尝试一下在类中不写注释和在方法中不写描述:
本次文章以实现为主,所以只简单提及了元类的使用 并未过多的介绍元类的概念以及他强大的功能.感兴趣的小伙伴可以去查看一下其他大佬分享的专门讲解元类的文章 想必会有更多的收获