用python自动生成代码
来源:互联网 发布:淘宝权在哪里直播现在 编辑:程序博客网 时间:2024/05/29 19:41
工作需要,要实现几十种事件的参数编辑界面,事件的数据结构如下:
// TsatEventstruct TSATDATASHARED_EXPORT TsatContingencyEvent{ virtual ~TsatContingencyEvent() {} virtual TsatContingencyEvent* clone() const = 0; virtual QString desc() const = 0;};// 母线三相短路struct TSATDATASHARED_EXPORT TsatEventThreePhaseFaultAtBus : public TsatContingencyEvent{ QString bus; virtual TsatContingencyEvent* clone() const; virtual QString desc() const;};// 线路三相短路struct TSATDATASHARED_EXPORT TsatEventThreePhaseFaultOnLine : public TsatContingencyEvent{ TsatEventThreePhaseFaultOnLine() :distance(0) { } QString fromBus; QString toBus; double distance; virtual TsatContingencyEvent* clone() const; virtual QString desc() const;};......如何相关的编辑界面,那是另一个话题,光是这几十个类的成员函数和xml文件的读写就够写吐血的了。难度不是问题,问题是太烦了,作为一个有着多年偷懒经验的老程序员,自然很快想到为了代码自动生成。那就用可爱的python来搞吧,原因很简单,一是这玩意不需要编译;二是这玩意有文本处理的利器正则表达式。
友情提示,这里是基于Python3.4版本的,还在用Python2的同学就不要直接拷代码了。
作为一个职业程序员的基本素养,动手编码之前当前要先设计一下,主要功能如下:
- 文件的读写是最基本的,可以直接复用已有函数
- 从C++的头文件读取类的信息,包括类的名称和基类的名称,所有的类信息应该组织为列表,以保证顺序
- 从C++的头文件读取类中成员变量的信息,包括成员变量的类型和名称,成员变量的信息应该组织在相应的类信息下面,这实际上就是几十个类元数据信息
- 根据类的元数据信息结合代码模板自动生成相应的代码
好了,可以开工了,打开PyCharm,建立相关的py文件。当然,如果你愿意用文本编辑工具,也随便你。
先把python的文件读写函数拿过来吧,这没啥好说的:
def loadArray(path, records, encoding): """加载文件到列表""" try: file = open(path, "r", encoding=encoding) # open file in read mode except IOError as message: # file open failed print("read file error({0}:{1})".format(message, path)) sys.exit(1) lines = file.readlines() for line in lines: records.append(line) file.close()def saveArray(path, records, encoding): """保存文件从列表""" try: file = open(path, "w", encoding=encoding) # open file in write mode except IOError as message: # file open failed print("write file error({0}:{1})".format(message, path)) sys.exit(1) file.writelines(records) file.close()这里没有把头文件中的文字作为一个整理进行处理,而是分解为一行行的处理,效率当然会低一些,但是后面的处理可以更自由一些
然后定义两个类,分别代表类的元数据和类成员函数的元数据,也非常简单:
class Var: def __init__(self, type, name): self.type = type self.name = nameclass Class: def __init__(self, name, desc, base): self.name = name self.desc = desc self.base = base self.vars = [] def appendVar(self, type, name): var = Var(type, name) self.vars.append(var)
下面就是重点了,定义cpp文件解析的类:
class ClassLib: def __init__(self): self.classes = [] self.classMaps = {}
其中classes存储类的元数据列表,classMap是为了搞一个从类名快速检索类元数据的结构,只是为了检索方便
既然要分析C++的头文件文件,自然要用到正则表达式了,因为这几个正则表达式只需要定义一次,就在类的构造函数中定义吧,当然更好一点的做法是做成属于类的静态变量,而不是属于对象的成员变量,不过也无所谓了,这个ClassLib基本上和单件也没啥区别的
reg = """ ^ #开头 (struct|class) #捕获类或者结构的关键字 \s+ #空格 [\w_]+ #捕获导出标记 \s+ #空格 (\w+) #类名 (.*) #或许有基类,注释什么的 $ #结束 """ self.classReg = re.compile(reg, re.VERBOSE) reg = """ \s*:\s* #空格:空格 (public|private|protected) #捕获派生的关键字 \s+ #空格 (\w+) #基类 """ self.baseClassReg = re.compile(reg, re.VERBOSE) reg = """ ^ #开头 \s+ #空格 (\w+) #变量类型 \s+ #空格 (\w+); #变量名称 .* #或许有注释什么的 $ #结束 """ self.dataReg = re.compile(reg, re.VERBOSE) reg = """ ^ #开头 // #// \s* #空格 (.+) #注释 $ #结束 """ self.commentReg = re.compile(reg, re.VERBOSE)
这些正则表达式都没啥技术含量,基本上都是简单粗暴的直接翻译,注释也写得很清楚。唯一需要说明的是这里把捕获类名和捕获基类名分成了两个部分,因为
TsatContingencyEvent是没有基类的
下面就开始解析C++的头文件文件吧:def parse(self, lines): object = None comment = "" for line in lines: m = re.match(self.commentReg, line) if m: comment = m.group(1) continue m = re.match(self.classReg, line) if m: name = m.group(2) base = None if m.lastindex >= 3: mm = re.match(self.baseClassReg, m.group(3)) if mm: base = self.classMaps[mm.group(2)] object = Class(name, comment, base) self.classes.append(object) self.classMaps[name] = object continue m = re.match(self.dataReg, line) if m: type = m.group(1) name = m.group(2) object.appendVar(type, name)代码很简单,注释都没必要写,就是发现是注释行则保存注释;发现是类定义行则捕获类名和基类名,然后生成类的元数据;发现时类的成员变量行则捕获成员变量的类型和名称,然后加入到类的元数据中
解析出来的类的元数据基本上就是一个简单的两层树形结构,再写一个print函数吧,方便测试和调试:
def print(self): for object in self.classes: base = "None" if object.base != None: base = object.base.name print("{0} : {1} // {2}".format(object.name, base, object.desc)) for var in object.vars: print("\t{0} {1};".format(var.type, var.name))好了,可以看主函数了:
srcPath = 'src.cpp'srcs = []loadArray(srcPath, srcs, 'utf-8')classLib = ClassLib()classLib.parse(srcs)classLib.print()没啥可说的,打印出来的结果是:
TsatContingencyEvent : None // TsatEventTsatEventThreePhaseFaultAtBus : TsatContingencyEvent // 母线三相短路QString bus;TsatEventThreePhaseFaultOnLine : TsatContingencyEvent // 线路三相短路QString fromBus;QString toBus;double distance;......好了,可以开始第4步了,根据类的元数据信息结合代码模板自动生成相应的代码:
def writeCpp(classLib, path): dsts = [] cpp = """ TsatContingencyEvent* $name$::clone() const { $name$ *ret = new $name$; *ret = *this; return ret; } QString $name$::desc() const { return QString(); } """ for object in classLib.classes: if object.base == None: continue base = object.base.name dsts.append(cpp.replace("$name$", object.name)) saveArray(path, dsts, 'utf-8')基本上就是一个关键字替换,当然具体的模板可以随便写,所以也不要纠结于为什么我这里的desc函数是直接返回QString()了,因为自动生成代码也不是万能的,不要为了全自动生成代码而增加一堆额外的工作,除非是一个系统的基础数据结构,后面需要不断的被自动化,那还可以考虑,至于这里的小工具嘛,就算了吧。
调用一下吧:
writeCpp(classLib, 'dst.cpp')
自动生成的代码是这个样子的:
TsatContingencyEvent* TsatEventThreePhaseFaultAtBus::clone() const { TsatEventThreePhaseFaultAtBus *ret = new TsatEventThreePhaseFaultAtBus; *ret = *this; return ret; } QString TsatEventThreePhaseFaultAtBus::desc() const { return QString(); } TsatContingencyEvent* TsatEventThreePhaseFaultOnLine::clone() const { TsatEventThreePhaseFaultOnLine *ret = new TsatEventThreePhaseFaultOnLine; *ret = *this; return ret; } QString TsatEventThreePhaseFaultOnLine::desc() const { return QString(); }......当然了,有的同学看得比较仔细,会一针见血的指出这个例子只用到类名嘛,那基类和类的成员函数的元数据根本没用到嘛。好吧,那就再举一个例子:
def writeFiler(classLib, path): dsts = [] cpp = """template<typename Filer>inline void filerElem(Filer& filer, $name$* data, QDomElement& elem){ filerElem(filer, dynamic_cast<$base$*>(data), elem); $vars$}""" for object in classLib.classes: if object.base == None: continue vars = [] for var in object.vars: type = var.type.title() if var.type == 'QString': type = 'String' vars.append('\tfiler.attribute{0}(elem, data->{1}, "{1}");'.format(type, var.name)) filer = cpp filer = filer.replace('$name$', object.name) filer = filer.replace('$base$', object.base.name) filer = filer.replace('$vars$', "\n".join(vars)[1:]) dsts.append(filer) saveArray(path, dsts, 'utf-8')其实就是多了一层循环,多替换一些关键字而已,也没啥难度。
再调用一下吧:
writeFiler(classLib, 'filer.cpp')
这次自动生成的代码是这个样子的:
template<typename Filer>inline void filerElem(Filer& filer, TsatEventRemoveLine* data, QDomElement& elem){ filerElem(filer, dynamic_cast<TsatContingencyEvent*>(data), elem); filer.attributeString(elem, data->fromBus, "fromBus"); filer.attributeString(elem, data->toBus, "toBus");}template<typename Filer>inline void filerElem(Filer& filer, TsatEventDisconnectGenerator* data, QDomElement& elem){ filerElem(filer, dynamic_cast<TsatContingencyEvent*>(data), elem); filer.attributeString(elem, data->bus, "bus");}......好了,怎么用python自动生成代码差多不也就这样了,没啥难度,但确实节约时间。我记得有人说过:珍爱生命,我用python。还有人说过,人生苦短,我用正则表达式。这两个东西这里都用到了,算是够怕死了吧,呵呵。
0 1
- 用python自动生成代码
- Python之自动生成代码继承关系
- 用Mybatis自动生成代码
- [转]python 代码自动生成的方法 (代码生成器)
- Python在C/C++代码自动生成中的使用
- ios拒审4.3 python自动生成辣鸡代码
- 用mybatis generator自动生成代码
- 用java代码自动生成数据库表
- 用maven插件自动生成mybatis代码
- 用maven插件自动生成mybatis代码
- 用maven插件自动生成mybatis代码
- 代码自动生成.
- 自动生成代码
- 代码自动生成软件
- ALV自动生成代码
- CodeSmith自动生成代码
- java代码自动生成
- 代码自动生成工具
- cannot resolve symbol R R丢失
- POJ 1064 Cable master 【二分】
- Codeforces Round #202 (Div. 1) A. Mafia
- 83.You are using Enterprise Manager to schedule backups for your database. Which type of script woul
- oracle的rownum原理和使用
- 用python自动生成代码
- 关于 sessionFacotry.getCurrentsession ,could not obtain transaction-synchronized这个错误的
- 阿里2015校招面试回忆录(成功拿到offer)
- Struts2的三种参数传递方式
- poj 2299 Ultra-QuickSort (归并求逆序数)
- Js制作移动漂浮广告
- Docker+Git+Jenkins+Cloud VM=CI+CD
- 通过隧道连接内网的PostgreSQL以及跨域与部署方案
- Populating Next Right Pointers in Each Node