根据传递的参数到xml中查找全类名,并反射获取类对象。这样对外只提供一个接口即可创建不同类的对象。
此功能实现分为两部分:读取xml和反射获取类对象。
python解析xml网上有很多教程,本文不再说明,可参考:Python XML 解析
公司项目的业务场景是:针对不同业务类型的excel文件,开发对应的pandas处理逻辑,每个处理逻辑封装成一个类。用户在调用处理程序时,仅需要传递一个excel的类型参数,程序根据参数到xml中查找对应的类名,反射创建类对象处理excel。
所以,项目中的xml文件大致是这样的:
在根元素class下,包含多个二级元素,二级元素的title表示excel的业务类型,如各种品牌的GMV使用不同格式的excel,所以使用不同的处理逻辑来处理这些excel。每种类型的处理逻辑封装在一个类中,这些而三级元素path则用于描述这些类的类名。
<!-- 根元素 --> <collection shelf="class"> <!-- 一级子元素class,用于描述业务逻辑类型的详细信息 --> <class title="lancome"> <!-- 二级子元素path,用于描述对应类的类名 --> <path>ClassOne</path> </class> <!-- 一级子元素class,用于描述项目中的类与类路径 --> <class title="loreal"> <path>ClassTwo</path> </class> </collection>1.使用minidom解析器打开 XML 文档
from xml.dom.minidom import parse DOMTree = parse(xmlPath) collection = DOMTree.documentElement 输出: <DOM Element: collection at 0x242b71e6178>2.获取名为class的根元素
xmlDocument = collection.getElementsByTagName("class") 输出: [<DOM Element: class at 0x1fe01546638>, <DOM Element: class at 0x1fe01546800>]3.在根元素中查找title为type1的二级元素,并获取其名为path的三级元素
# 遍历所有节点的详细信息中搜索二级节点 for eName in classes: if eName.getAttribute("title") == "type1": # 获取名为path的所有子节点中的第一个子节点的第一个子节点(拗口哟) className = eName.getElementsByTagName("path")[0].childNodes[0].data break print("className") 输出: ClassOne
python中可使用getattr方法获取模块中的子模块,getattr方法包含两个参数:模块名,子模块名。
getattr会在指定模块中查找指定的子模块名,如:项目中有一个DataCompare模块,在这个模块中有一个ClassOne.py文件,在ClassOne.py文件中定义了一个名为ClassOne的类。
那么,可以使用getattr方法在DataCompare中查找ClassOne.py:
module = getattr(DataCompare, "ClassOne") 输出: <module 'DataCompare.ClassOne' from 'E:\\code\\python\\Loreal\\DataCompare\\ClassOne.py'>然后在ClassOne.py中查找ClassOne类:
getattr(module ,"ClassOne") 输出: <class 'DataCompare.ClassOne.ClassOne'>或
getattr(getattr(DataCompare,"ClassOne"), "ClassOne")将读取xml功能封装在一个XmlUtils.py工具模块中:
# -*- coding: UTF-8 -*- from xml.dom.minidom import parse def getXML(xmlPath): # 使用minidom解析器打开 XML 文档 DOMTree = parse(xmlPath) collection = DOMTree.documentElement return collection def getElement(xml, name1st, name2st): # 获取xml对象中一级节点的所有子节点集合 classes = xml.getElementsByTagName(name1st) className = None # 遍历所有节点的详细信息中搜索二级节点 for eName in classes: if eName.getAttribute("title") == name2st: # 获取名为path的所有子节点中的第一个子节点的第一个子节点(拗口哟) className = eName.getElementsByTagName("path")[0].childNodes[0].data break if className == None: print("没有找到这个类,请检查xml文件") return className将反射创建类对象封装进一个ClassHandler.py中:
# -*- coding: UTF-8 -*- import DataCompare import DataCompare.XmlUtils as xml class ClassHandler: def getClass(className): #getattr有两个参数,左边参数是模块名,右边参数是模块中的子模块名 #getattr返回的是类对象,加个()创建实例 return getattr(getattr(DataCompare,className), className)()需要在DataCompare模块的__init__.py文件中导入DataCompare中的ClassOne等模块,否则会报找不到类对象的错误。__init__.py文件用于一些初始化工作,它不仅表示一个文件夹是一个python包,还会在导入这个模块时执行__init__.py里的代码:
#init文件用于标识这是一个py包,并且可以在init文件中导入包或者做一些初始化工作,这样在导入这个包时会自动执行 from DataCompare import ClassOne,ClassTwo在ClassOne中定义WhoAmI方法打印测试:
class ClassOne: def WhoAmI(self): print("class 1")调用XmlUtils读取xml,并使用返回的类名调用ClassHandler创建实例:
# -*- coding: UTF-8 -*- import DataCompare.ClassHandler as ch from DataCompare.XmlUtils import * class ClassHandler: if __name__ == '__main__': className = getElement(getXML("conf/class.xml"),"class","type1") excutor = ch.getClass(className) excutor.WhoAmI() 输出: class 1