需求
根据传递的参数到xml中查找全类名,并反射获取类对象。这样对外只提供一个接口即可创建不同类的对象。
实现
此功能实现分为两部分:读取xml和反射获取类对象。
解析xml文件
python解析xml网上有很多教程,本文不再说明,可参考:Python XML 解析
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>
读取xml
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