当前位置:首页 > 数码 > 模块调用相关剖析-Python脚本促成OC代码重构通常 (模块的调用关系)

模块调用相关剖析-Python脚本促成OC代码重构通常 (模块的调用关系)

admin4个月前 (05-08)数码17

在软件开发中,经常会遇到一些代码疑问,例如逻辑结构复杂、依赖相关凌乱、代码冗余、不易读懂的命名等。这些疑问或者造成代码的可保养性降低,参与保养老本,同时也会影响到开发效率。

这时通常经过重构的方式,在不扭转软件的性能和行为的前提下,对软件的代码启动从新组织和优化。到达增强代码的可读性,降低保养老本,优化研发效率和品质的目的。经过正当的重构,可以大大提高软件的可保养性和可裁减性,从而延伸其生命。

本系列的内容引见了百度搜查侧业务如何经常使用/target=_blankclass=infotextkey>Python脚本成功智能化工具,以允许百度App性能数据项调用方式更新为数据通路的重构环节。经过Python脚本,咱们成功一些智能化的工具,包括性能数据项调用相关剖析、性能数据项接入数据通路的成功、数据项经常使用方接入数据通路的适配等,以期提高上班效率、缩小出错率。

01、代码重构时的关键步骤及应战

在代码重构环节中,须要思考重构的效率和重构后的代码品质。与其相关的关键的步骤如下,这些步骤先后依赖,相互影响:

相熟业务及技术现状:在开局重构之前,研发首先要了解业务逻辑和流程,相熟业务才干及技术成功时存在的疑问,确定重构的范围。

确定重构打算:基于对业务逻辑和现有代码疑问的了解,确定重构打算,重点关注有两点,有疑问的代码如何重构和依赖于该代码的调用如何适配。

分阶段实施:依据重构打算,分阶段的修正代码,并测试代码的性能能否反常。在修正环节中,应该尽量防止影响到不相关模块,这样可以更好地控制危险。

成果评价及监控:重构打算开发成功,线下对成功的成果启动评价,线上对成功的成果启动监控,及时发现意外止损和重构的成果。

在重构的上班中,大局部的上班是人工的方式成功,是一个耗时且容易出错的环节。关于研发人员来讲,在不扭转软件的性能和行为的前提下,保障品质和效率成功对已有性能的重构,是一个极大的应战。

02、百度App()搜查侧的性能数据项重构

为了更好的优化系统稳固性和降低性能数据项变卦时对下层依赖方组件的影响,咱们选择对百度App(iOS)搜查侧的性能数据项启动重构。重构环节的关键节点中有超越80%的上班是由智能化工具成功,允许重构上班上线后零bug,和所有的性能数据项接口内敛,优化了系统的安保性和稳固性。

2.1重构背景

百度App(iOS)-搜查侧的性能数据项,大局部集中在一个类(XXXSetting)中治理。该类(XXXSetting)以独立组件的方式颁布,被超越30个其它组件依赖。

如图-1所示数据项经常使用模块间接调用数据项提供模块(XXXSetting),是间接依赖的相关,数据项的增删相当于接口的变卦,对下层的依赖方会发生影响,当接口存在不兼容变卦时,连带下层的依赖方组件也须要二次的颁布。且该组件中的数据项关键为试验类开关,变化较为频繁,影响面也被加大,故需经常使用比拟稳固的方式成功不同模块之间的数据项共享。

图-1

2.2技术打算

在技术成功的层面,关键分为两步

1、第一步为成功多模块之间数据通信的模块,在本系列的内容中以数据通路代指该模块。

2、第二步为基于数据通路提供的才干,XXXSetting组件为作数据提供方接入数据通路,原经常使用XXXSetting组件的经常使用方接入数据通路,这样就成功了XXXSetting组件中的数据项迁徙。

数据通路的成功,目的成功以Key-Value的方式读取及更新性能项,须要从无到有的构建,在本系列的其它章节中内容会有引见。但XXXSetting组件对应的重构上班,是基于已有的线上才干的变革,Setting中的数据项超越百个,外部的调用点也是以百为计算单位,触及的组件有30+。影响面如何评价,如何保障重构的环节品质和成果是可控的?联合对重构环节的了解,咱们驳回了Python脚原本允许第二步的上班。

2.3经常使用Python允许重构环节

要规避以人工方式为主的重构环节,引入失误的危险,优化重构环节的品质及效率。须要引入Python脚本成功智能化工具允许重构环节的上班。上方以重构的关键步骤,智能化工具的运行目的启动罗列。

1、在相熟业务及技术现状阶段,可以经常使用智能化工具对工程中现有的代码、技术架构启动剖析,失掉以后须要重构的代码的依赖和调用相关消息,确定重构环节的变化影响,经常使用智能化的方式会愈加的精准。

2、在确定重构打算阶段,可以基于智能化工具发生的数据,允许重构打算的决策,包括能否须要重构,如何重构,调用方如何适配等。

3、在分阶段实施阶段,可以经常使用智能化的方式允许代码的重构上班,包括须要重构的模块的更新、调用方代码的适配等。对比IDE提供的查找、交流等基础工具,智能化工具可以批量处置愈加复杂的重构上班。同时实施的阶段通常是繁琐且容易出错的,但经常使用智能化的方式可以智能成功这些义务,并缩君子为失误。

4、在成果评价及监控阶段,可以经常使用智能化的方式对重构前后的代码启动对比测试保障性能的分歧性,搜集关键目的数据,发现目的的意外。

03、用Python脚本成功模块的调用关析剖析

在实践的性能数据项的调用相关来看,地下的数据项可为几种状况,对应的重构打算可有不同。

1、性能数据项仅在XXXSetting模块内经常使用,这局部数据项不须要接入数据通路。

2、性能数据项在XXXSetting模块内经常使用,也在其它的模块中经常使用,这类数据项在XXXSetting模块中保养,数据项须要接入数据通路。

3、性能数据项在XXXSetting模块内没有经常使用,只在一个模块中经常使用,这类数据项应该迁徙到经常使用该数据项的模块中。

4、性能数据项在XXXSetting模块内没有经常使用,但在一个以上模块中经常使用,这类数据项可以在XXXSetting模块中保养,但数据项须要接入数据通路。

基于这样的变革,XXXSetting模块的数据项接口就可以所有不地下,关于性能数据项的变卦,只影响依赖性能数据项的模块。那么每个数据项的调用应该是如何重构呢,用手动查找及剖析的方式老本过高,在名目实践环节评价及修正出错的概率也会增高,咱们经常使用Python脚本成功了调用相关的剖析工具,为重构上班提早启动数据允许及决策。

3.1提取地下数据项及类型

在剖析数据项的外部调用状况之前,须要先提取XXXSetting类中一切地下的数据项。

3.1.1地下数据项在OC类中的写法

Setting文件由OC言语开发,在Setting头文件件中地下的数据项的定义,OC类中成员变量的定义,书写方式如下

@property(nonatomic,assign)BOOLvalue;@property(nonatomic,copy)NSString*value1

3.1.2提取的是变量类型和变量的称号

因头文件中,蕴含其它非成员变量的代码,比如include、前置申明、类定义、空代码行、注释、函数等,须要预处置下代码及经常使用正则表白式变量定义代码段,依次的读取.h文件中的每一行代码,以相关成功及的关键代码如下。

去除注释

因代码中的注释写法存在不确定性,会对前面的正则婚配发生影响,故先把注释删除。

#原代码行@property(nonatomic,copy)NSString*value1;//注释;*()这些字符都有或者有,会影响前面的正则判别newline=re.sub(r'//.+',"",line)#处置事先的代码行@property(nonatomic,copy)NSString*value1;
提取数据项类型及数据项

去除注释代码之后,下一步为提取成员变量称号及类型,可以经常使用正则中的分组婚配的才干,提取变量类型及变量名。这里经常使用了正则的要素是代码的写法存在不确定性,@property的写法也会因变量类型不同而变化,故经过火组婚配的方式来成功。

#原代码行@property(nonatomic,copy)NSString*value1;matchObj=re.match(r"@property.+)s+(.*)",line,re.M|re.I)ifmatchObj:#matchObj.group(1)是成员变量类型和变量名--NSString*value1;

去除无用字符

这时的代码行,由于写法的不同及变量的不同,须要启动规范化,才干提取出变量类型及变量名,关键为去除星号(*)。代码行头中的空格曾经过滤(上传代码中的s+)。

#原代码行NSString*value1;newline=line.replace('*','')#处置后的代码行NSStringvalue1;

提取规范化后的数据项类型及数据项

这时代码行中只剩下类型空格变量名分号,经常使用正则的分组婚配,提取类型及变量名。

#原代码行NSStringvalue1;#正则表白式中s婚配任何空白字符,包括空格、制表符、换页符等等,等价于[fnrtv],s+代表一个或多个这类的字符matchObj=re.match(r"(.*)s+(.*);",line,re.M|re.I)ifmatchObj:#valueType=NSStringvalueType=matchObj.group(1)#valueName=value1valueName=matchObj.group(2)

到这了一步,地下可访问的数据项及类型的提取就已级成功,这时就可以转换代码,假设这时转换代码,会存在冗余,由于假设地下的变量在其它模块中没有经常使用,那实践上就不须要经常使用数据通路启动封装,下一步应该剖析调用相关之后,再启动。

3.2数据项关联调用组件

确定了地下的数据项之后,须要在工程源码中查找每个数据项的调用点,之后再跟据调用点数据确定每个数据项在不同的组件中调用的状况。

数据项调用代码经常出现于以下写法,OC中也有其它的写法,本文中以下写法作为示例引见调用相关的生成。

[XXXSettingshare].value1

3.2.1.查找每个数据项在文件中的调用

#定义个全局字典,寄存每个数据项在不同的文件中调用的次数#{数据项:{文件名:该文件内数据项调用的次数}}valueCallInfoDic={}#经常使用上节中,提取进去的数据项名,拼装为实践和谐时的写法realValueName='[XXXSettingshare].'+valueName#fileNameList为一切源码文件(.m和.mm)forfileNameinfileNameList:#记载该文件调用数据项的次数callNum=0#记载文件每个文件调用该数据项的次数消息fileCallInfoDic={}#依次的读取源文件的每一行,婚配调用状况,记载调用次数,及文件名,line为代码行forlineinf:#经常使用正则全字婚配,查找交流regAbKey=realValueName.replace('[','[')regAbKey=regAbKey.replace(']',']')regAbKey=regAbKey.replace('.','.')#pattern=[XXXSettingshare].value1b关键为了防止数据项名有子串的状况pattern=r''+fromstr+r'b'matchObj=re.match(r'.*'+regAbKey+'',line,re.M|re.I)ifmatchObj:callNum=callNum+1ifcallNum>0fileCallInfoDic[fileName]=str(callNum)#假设有调用相关,则存储iflen(fileCallInfoDic)valueCallInfoDic[valueName]=fileCallInfoDic

3.3输入为表格文件

经常使用Python剖析的数据还是以机器言语的方式表式,须要以人类言语形容,将数据输入为excel表格,这样就可以借助于表格工具启动数据的检查及剖析。

3.3.1数据项的详细经常使用状况输入

表格的输入Python没有经常使用有excel操作的相关库,经常使用,(逗号)作为分隔符,存储为.csv文件,在excel中导入csv文件经常使用。

详细的成功为依次的将每个数据项的经常使用的组件,经常使用的文件及在这个文件文件中经常使用次数,输入到.csv文件中。

#表头区分为,数据项,经常使用的组件,经常使用的文件,文件中经常使用次数outfiledata='value,uselib,usefile,usenumn'#遍历全局字典valueCallInfoDic,失掉每个数据项及数据项的调用消息#{数据项:{文件名:该文件内数据项调用的次数}}for.valueName,valueInfoinvalueCallInfoDic.items():#从数据项的调用消息中失掉,文件名和该文件内数据项调用的次数#{文件名:该文件内数据项调用的次数}for.fileName,callNuminvalueCallInfoDic.items():outfiledata+=valueName+","#libByFile函数,成功依据文件失掉所在的组件名outfiledata+=libByFile(fileName)+","outfiledata+=fileName+","outfiledata+=callNum+"n"
表格数据示例

基于输入的表格数据,可以比拟容易的判别每个数据项的优化影响范围,下表为表格数据的示例。

△注:表格数据非实在业务场景数据

3.3.2数据项的预剖析统计输入

基于数据的调用相关数据,确定每个数据项被每个组件经常使用的状况,并确定重构的方式。

雷同,表格的输入Python没有经常使用有excel操作的相关库,经常使用,(逗号)作为分隔符,存储为.csv文件,在excel中导入csv文件经常使用。

详细的成功为依次的读取数据项,计算每个数据项被组件的经常使用状况,并将结果输入到.csv文件中。

#表头区分为,数据项,经常使用的组件,组件中总经常使用次数,经常使用类型outfiledata='value,uselib,usenum,usetypen'#遍历全局字典,失掉每个数据项及数据项的调用消息#{数据项:{文件名:该文件内数据项调用的次数}}for.valueName,valueInfoinvalueCallInfoDic.items():libCallInfo={}#从数据项的调用消息中失掉,文件名和该文件内数据项调用的次数#{文件名:该文件内数据项调用的次数}for.fileName,callNuminvalueCallInfoDic.items():#libByFile函数,成功依据文件失掉所在的组件名libName=libByFile(fileName)iflibNameinlibCallInfo:libCallInfo[libName]=int(libCallInfo[libName])+int(callNum)else:libCallInfo[libName]=callNum#每个组件的经常使用XXXSetting的数据项状况hasSelfCall=FalseuseType=""for.libNameinlibCallInfo:iflibName=="XXXSetting":hasSelfCall=Truebreakiflen(libCallInfo)==1:ifhasSelfCall:#性能数据项仅在XXXSetting模块内经常使用,这局部数据项不须要接入数据通路。useType="selfCall"else:#性能数据项在XXXSetting模块内没有经常使用,只在一个模块中经常使用,这类数据项应该迁徙到经常使用该数据项的模块中。useType="otherCall"else:ifhasSelfCall:#性能数据项在XXXSetting模块内经常使用,也在其它的模块中经常使用,这类数据项在XXXSetting模块中保养,数据项须要接入数据通路。useType="selfAndOtherCall"else:#性能数据项在XXXSetting模块内没有经常使用,但在一个以上模块中经常使用,这类数据项可以在XXXSetting模块中保养,但数据项须要接入数据通路。useType="othersCall"for.libName,libCallNuminlibCallInfo.items():outfiledata+=valueName+","outfiledata+=libName+","outfiledata+=libCallNum+"n"
表格数据示例

基于输入的表格数据,可以比拟容易的判别每个数据项应该如何整改,下表为表格数据的示例。

注:表格数据非实在业务场景数据

04小结

以上的内容,引见了代码重构环节的上班及应战,同时以Python脚本成功剖析模块的调用相关的统计,基于该脚本,在重构上班开局之前,可以准确统计每个XXXSetting类对当地下的类成员属性,被其它组件经常使用的状况。基于统计的数据,可以感知对应的每个成员属性在App中的经常使用状况,且可容易的评价XXXSetting数据项重构更新为数据通路上班所带来的影响。

当这局部上班,经常使用人工的方式成功,依次查找每个成员属性的在App中的经常使用状况及分类记载,是一件重复性高,出错概率高的上班。而经常使用智能化工具,很好的规避了这些疑问,且常年可积攒。


python 怎么调用文件中的模块

Python 模块模块让你能够有逻辑地组织你的Python代码段。 把相关的代码分配到一个 模块里能让你的代码更好用,更易懂。 模块也是Python对象,具有随机的名字属性用来绑定或引用。 简单地说,模块就是一个保存了Python代码的文件。 模块能定义函数,类和变量。 模块里也能包含可执行的代码。 例子一个叫做aname的模块里的Python代码一般都能在一个叫的文件中找到。 下例是个简单的模块。 def print_func( par ):print Hello : , parreturnimport 语句想使用Python源文件,只需在另一个源文件里执行import语句,语法如下:import module1[, module2[,... moduleN]当解释器遇到import语句,如果模块在当前的搜索路径就会被导入。 搜索路径是一个解释器会先进行搜索的所有目录的列表。 如想要导入模块,需要把命令放在脚本的顶端:#!/usr/bin/python# -*- coding: UTF-8 -*-# 导入模块import support# 现在可以调用模块里包含的函数了_func(Zara)

后端编程Python3-调试、测试和性能剖析(下)

单元测试(Unit Testing)

为程序编写测试——如果做的到位——有助于减少bug的出现,并可以提高我们对程序按预期目标运行的信心。通常,测试并不能保证正确性,因为对大多数程序而言, 可能的输入范围以及可能的计算范围是如此之大,只有其中最小的一部分能被实际地进 行测试。尽管如此,通过仔细地选择测试的方法和目标,可以提高代码的质量。

大量不同类型的测试都可以进行,比如可用性测试、功能测试以及整合测试等。这里, 我们只讲单元测试一对单独的函数、类与方法进行测试,确保其符合预期的行为。

模块的调用关系

TDD的一个关键点是,当我们想添加一个功能时——比如为类添加一个方法—— 我们首次为其编写一个测试用例。当然,测试将失败,因为我们还没有实际编写该方法。现在,我们编写该方法,一旦方法通过了测试,就可以返回所有测试,确保我们新添加的代码没有任何预期外的副作用。一旦所有测试运行完毕(包括我们为新功能编写的测试),就可以对我们的代码进行检查,并有理有据地相信程序行为符合我们的期望——当然,前提是我们的测试是适当的。

比如,我们编写了一个函数,该函数在特定的索引位置插入一个字符串,可以像下面这样开始我们的TDD:

def insert_at(string, position, insert):

Returns a copy of string with insert inserted at the position

>>> string = ABCDE

>>> result =[]

>>> for i in range(-2, len(string) + 2):

... (insert_at(string, i,“-”))

>>> result[:5]

[ABC-DE, ABCD-E, -ABCDE,A-BCDE, AB-CDE]

>>> result[5:]

[ABC-DE, ABCD-E, ABCDE-, ABCDE-]

return string

对不返回任何参数的函数或方法(通常返回None),我们通常赋予其由pass构成的一个suite,对那些返回值被试用的,我们或者返回一个常数(比如0),或者某个不变的参数——这也是我们这里所做的。(在更复杂的情况下,返回fake对象可能更有用一一对这样的类,提供mock对象的第三方模块是可用的。)

运行doctest时会失败,并列出每个预期内的字符串(ABCD-EF、ABCDE-F 等),及其实际获取的字符串(所有的都是ABCD-EF)。一旦确定doctest是充分的和正确的,就可以编写该函数的主体部分,在本例中只是简单的return string[:position] + insert+string[position:]。(如果我们编写的是 return string[:position] + insert,之后复制 string [:position]并将其粘贴在末尾以便减少一些输入操作,那么doctest会立即提示错误。)

Python的标准库提供了两个单元测试模块,一个是doctest,这里和前面都简单地提到过,另一个是unittest。此外,还有一些可用于Python的第三方测试工具。其中最著名的两个是nose (/p/python-nose)与 (/py/dist/test/), nose 致力于提供比标准的unittest 模块更广泛的功能,同时保持与该模块的兼容性,则采用了与unittest有些不同的方法,试图尽可能消除样板测试代码。这两个第三方模块都支持测试发现,因此没必要写一个总体的测试程序——因为模块将自己搜索测试程序。这使得测试整个代码树或某一部分 (比如那些已经起作用的模块)变得很容易。那些对测试严重关切的人,在决定使用哪个测试工具之前,对这两个(以及任何其他有吸引力的)第三方模块进行研究都是值 得的。

创建doctest是直截了当的:我们在模块中编写测试、函数、类与方法的docstrings。 对于模块,我们简单地在末尾添加了 3行:

if __name__ ==__main__:

import doctest

在程序内部使用doctest也是可能的。比如,程序(其模块在后面)有自己函数的doctest,但以如下代码结尾:

if __name__== __main__:

这里简单地调用了程序的main()函数,并且没有执行程序的doctest。要实验程序的 doctest,有两种方法。一种是导入doctest模块,之后运行程序---比如,在控制台中输 入 python3 -m doctest (在 Wndows 平台上,使用类似于 C:Python3 这样的形式替代python3)。如果所有测试运行良好,就没有输出,因此,我们可能宁愿执行python3-m doctest -v,因为这会列出每个执行的doctest,并在最后给出结果摘要。

另一种执行doctest的方法是使用unittest模块创建单独的测试程序。在概念上, unittest模块是根据Java的JUnit单元测试库进行建模的,并用于创建包含测试用例的测试套件。unittest模块可以基于doctests创建测试用例,而不需要知道程序或模块包含的任何事物——只要知道其包含doctest即可。因此,为给程序制作一个测试套件,我们可以创建如下的简单程序(将其称为test_):

import doctest

import unittest

import blocks

((blocks))

print((suite))

注意,如果釆用这种方法,程序的名称上会有一个隐含的约束:程序名必须是有效的模块名。因此,名为的程序的测试不能写成这样。因为import convert-incidents不是有效的,在Python标识符中,连接符是无效的(避开这一约束是可能的,但最简单的解决方案是使用总是有效模块名的程序文件名,比如,使用下划线替换连接符)。这里展示的结构(创建一个测试套件,添加一个或多个测试用例或测试套件,运行总体的测试套件,输出结果)是典型的机遇unittest的测试。运行时,这一特定实例产生如下结果:

Ran 3 tests in 0.244s

每次执行一个测试用例时,都会输出一个句点(因此上面的输出最前面有3个句点),之后是一行连接符,再之后是测试摘要(如果有任何一个测试失败,就会有更多的输出信息)。

如果我们尝试将测试分离开(典型情况下是要测试的每个程序和模块都有一个测试用例),就不要再使用doctests,而是直接使用unittest模块的功能——尤其是我们习惯于使用JUnit方法进行测试时ounittest模块会将测试分离于代码——对大型项目(测试编写人员与开发人员可能不一致)而言,这种方法特别有用。此外,unittest单元测试编写为独立的Python模块,因此,不会像在docstring内部编写测试用例时受到兼容性和明智性的限制。

unittest模块定义了 4个关键概念。测试夹具是一个用于描述创建测试(以及用完之后将其清理)所必需的代码的术语,典型实例是创建测试所用的一个输入文件,最后删除输入文件与结果输出文件。测试套件是一组测试用例的组合。测试用例是测试的基本单元—我们很快就会看到实例。测试运行者是执行一个或多个测试套件的对象。

典型情况下,测试套件是通过创建的子类实现的,其中每个名称 以“test”开头的方法都是一个测试用例。如果我们需要完成任何创建操作,就可以在一个名为setUp()的方法中实现;类似地,对任何清理操作,也可以实现一个名为 tearDown()的方法。在测试内部,有大量可供我们使用的方法,包括 assertTrue()、assertEqual()、assertAlmostEqual()(对于测试浮点数很有用)、assertRaises() 以及更多,还包括很多对应的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、 failUnlessEqual ()等。

unittest模块进行了很好的归档,并且提供了大量功能,但在这里我们只是通过一 个非常简单的测试套件来感受一下该模块的使用。这里将要使用的实例,该练习要求创建一个Atomic模块,该模块可以用作一 个上下文管理器,以确保或者所有改变都应用于某个列表、集合或字典,或者所有改变都不应用。作为解决方案提供的模块使用30行代码来实现Atomic类, 并提供了 100行左右的模块doctest。这里,我们将创建test_模块,并使用 unittest测试替换doctest,以便可以删除doctest。

在编写测试模块之前,我们需要思考都需要哪些测试。我们需要测试3种不同的数据类型:列表、集合与字典。对于列表,需要测试的是插入项、删除项或修改项的值。对于集合,我们必须测试向其中添加或删除一个项。对于字典,我们必须测试的是插入一个项、修改一个项的值、删除一个项。此外,还必须要测试的是在失败的情况下,不会有任何改变实际生效。

结构上看,测试不同数据类型实质上是一样的,因此,我们将只为测试列表编写测试用例,而将其他的留作练习。test_模块必须导入unittest模块与要进行测试的Atomic模块。

创建unittest文件时,我们通常创建的是模块而非程序。在每个模块内部,我们定义一个或多个子类。比如,test_模块中仅一个单独的 unittest-TestCase子类,也就是TestAtomic (稍后将对其进行讲解),并以如下两行结束:

if name == __main__:

这两行使得该模块可以单独运行。当然,该模块也可以被导入并从其他测试程序中运行——如果这只是多个测试套件中的一个,这一点是有意义的。

如果想要从其他测试程序中运行test_模块,那么可以编写一个与此类似的程序。我们习惯于使用unittest模块执行doctests,比如:

import unittest

import test_Atomic

suite = ()(test_)

pnnt((suite))

这里,我们已经创建了一个单独的套件,这是通过让unittest模块读取test_Atomic 模块实现的,并且使用其每一个test*()方法(本实例中是test_list_success()、test_list_fail(),稍后很快就会看到)作为测试用例。

我们现在将查看TestAtomic类的实现。对通常的子类(不包括 子类),不怎么常见的是,没有必要实现初始化程序。在这一案例中,我们将需要建立 一个方法,但不需要清理方法,并且我们将实现两个测试用例。

def setUp(self):

_list = list(range(10))

我们已经使用了 ()方法来创建单独的测试数据片段。

def test_list_succeed(self):

items = _list[:]

with (items) as atomic:

del atomic[5]

atomic[4]= -782

[-9, 0, 1, -915, 2, -782, 5, 6, 7, 8, 9, 1999])

def test_list_fail(self):

items = _list[:]

with (AttributeError):

with (items) as atomic:

del atomic[5]

atomic[4] = -782

(items, _list)

这里,我们直接在测试方法中编写了测试代码,而不需要一个内部函数,也不再使用()作为上下文管理器(期望代码产生AttributeError)。 最后我们也使用了 Python 3.1 的 ()方法。

正如我们已经看到的,Python的测试模块易于使用,并且极为有用,在我们使用 TDD的情况下更是如此。它们还有比这里展示的要多得多的大量功能与特征——比如,跳过测试的能力,这有助于理解平台差别——并且这些都有很好的文档支持。缺失的一个功能——但nose与提供了——是测试发现,尽管这一特征被期望在后续的Python版本(或许与Python 3.2—起)中出现。

性能剖析(Profiling)

如果程序运行很慢,或者消耗了比预期内要多得多的内存,那么问题通常是选择的算法或数据结构不合适,或者是以低效的方式进行实现。不管问题的原因是什么, 最好的方法都是准确地找到问题发生的地方,而不只是检査代码并试图对其进行优化。 随机优化会导致引入bug,或者对程序中本来对程序整体性能并没有实际影响的部分进行提速,而这并非解释器耗费大部分时间的地方。

在深入讨论profiling之前,注意一些易于学习和使用的Python程序设计习惯是有意义的,并且对提高程序性能不无裨益。这些技术都不是特定于某个Python版本的, 而是合理的Python程序设计风格。第一,在需要只读序列时,最好使用元组而非列表; 第二,使用生成器,而不是创建大的元组和列表并在其上进行迭代处理;第三,尽量使用Python内置的数据结构 dicts、lists、tuples 而不实现自己的自定义结构,因为内置的数据结构都是经过了高度优化的;第四,从小字符串中产生大字符串时, 不要对小字符串进行连接,而是在列表中累积,最后将字符串列表结合成为一个单独的字符串;第五,也是最后一点,如果某个对象(包括函数或方法)需要多次使用属性进行访问(比如访问模块中的某个函数),或从某个数据结构中进行访问,那么较好的做法是创建并使用一个局部变量来访问该对象,以便提供更快的访问速度。

Python标准库提供了两个特别有用的模块,可以辅助调査代码的性能问题。一个是timeit模块——该模块可用于对一小段Python代码进行计时,并可用于诸如对两个或多个特定函数或方法的性能进行比较等场合。另一个是cProfile模块,可用于profile 程序的性能——该模块对调用计数与次数进行了详细分解,以便发现性能瓶颈所在。

为了解timeit模块,我们将查看一些小实例。假定有3个函数function_a()、 function_b()、function_c(), 3个函数执行同样的计算,但分别使用不同的算法。如果将这些函数放于同一个模块中(或分别导入),就可以使用timeit模块对其进行运行和比较。下面给出的是模块最后使用的代码:

if __name__ == __main__:

repeats = 1000

for function in (function_a, function_b, function_c):

t = ({0}(X, Y)(function),from __main__ import {0}, X, (function))

sec = (repeats) / repeats

print({function}() {sec:.6f} (**locals()))

赋予()构造子的第一个参数是我们想要执行并计时的代码,其形式是字符串。这里,该字符串是“function_a(X,Y)”;第二个参数是可选的,还是一个待执行的字符串,这一次是在待计时的代码之前,以便提供一些建立工作。这里,我们从 __main__ (即this)模块导入了待测试的函数,还有两个作为输入数据传入的变量(X 与Y),这两个变量在该模块中是作为全局变量提供的。我们也可以很轻易地像从其他模块中导入数据一样来进行导入操作。

调用对象的timeit()方法时,首先将执行构造子的第二个参数(如果有), 之后执行构造子的第一个参数并对其执行时间进行计时。()方法的返回值是以秒计数的时间,类型是float。默认情况下,timeit()方法重复100万次,并返回所 有这些执行的总秒数,但在这一特定案例中,只需要1000次反复就可以给出有用的结果, 因此对重复计数次数进行了显式指定。在对每个函数进行计时后,使用重复次数对总数进行除法操作,就得到了平均执行时间,并在控制台中打印出函数名与执行时间。

function_a() 0. sec

function_b() 0. sec

function_c() 0. sec

在这一实例中,function_a()显然是最快的——至少对于这里使用的输入数据而言。 在有些情况下一一比如输入数据不同会对性能产生巨大影响——可能需要使用多组输入数据对每个函数进行测试,以便覆盖有代表性的测试用例,并对总执行时间或平均执行时间进行比较。

有时监控自己的代码进行计时并不是很方便,因此timeit模块提供了一种在命令行中对代码执行时间进行计时的途径。比如,要对模块中的函数function_a()进行计时,可以在控制台中输入如下命令:python3 -m timeit -n 1000 -s from MyModule import function_a, X, Y function_a(X, Y)(与通常所做的一样,对 Windows 环境,我们必须使用类似于这样的内容来替换python3)。-m选项用于Python 解释器,使其可以加载指定的模块(这里是timeit),其他选项则由timeit模块进行处理。 -n选项指定了循环计数次数,-s选项指定了要建立,最后一个参数是要执行和计时的代码。命令完成后,会向控制台中打印运行结果,比如:

1000 loops, best of 3: 1.41 msec per loop

之后我们可以轻易地对其他两个函数进行计时,以便对其进行整体的比较。

cProfile模块(或者profile模块,这里统称为cProfile模块)也可以用于比较函数 与方法的性能。与只是提供原始计时的timeit模块不同的是,cProfile模块精确地展示 了有什么被调用以及每个调用耗费了多少时间。下面是用于比较与前面一样的3个函数的代码:

if __name__ == __main__:

for function in (function_a, function_b, function_c):

(for i in ranged 1000): {0}(X, Y)(function))

我们必须将重复的次数放置在要传递给()函数的代码内部,但不需要做任何创建,因为模块函数会使用内省来寻找需要使用的函数与变量。这里没有使用显式的print()语句,因为默认情况下,()函数会在控制台中打印其输出。下面给出的是所有函数的相关结果(有些无关行被省略,格式也进行了稍许调整,以便与页面适应):

1003 function calls in 1.661 CPU seconds

ncalls tottime percall cumtime percall filename:lineno(function)

1 0.003 0.003 1.661 1.661:1 ( )

1000 1.658 0.002 1.658 0.002 :21 (function_a)

1 0.000 0.000 1.661 1.661 {built-in method exec}

function calls in 22.700 CPU seconds

ncalls tottime percall cumtime percall filename:lineno(function)

1 0.487 0.487 22.700 22.700: 1 ( )

1000 0.011 0.000 22.213 0.022 :28(function_b)

7.048 0.000 7.048 0.000 :29( )

1000 0.00 50.000 0.005 0.000 {built-in method bisectjeft}

1 0.000 0.000 22.700 22.700 {built-in method exec}

1000 0.001 0.000 0.001 0.000 {built-in method len}

1000 15.149 0.015 22.196 0.022 {built-in method sorted}

function calls in 12.987 CPU seconds

ncalls tottime percall cumtime percall filename:lineno(function)

1 0.205 0.205 12.987 12.987:l ( )

1000 6.472 0.006 12.782 0.013 :36(function_c)

6.311 0.000 6.311 0.000 :37( )

1 0.000 0.000 12.987 12.987 {built-in method exec}

ncalls (调用的次数)列列出了对指定函数(在filename:lineno(function)中列出) 的调用次数。回想一下我们重复了 1000次调用,因此必须将这个次数记住。tottime (“总的时间”)列列出了某个函数中耗费的总时间,但是排除了函数调用的其他函数内部花费的时间。第一个percall列列出了对函数的每次调用的平均时间(tottime // ncalls)。 cumtime (累积时间)列出了在函数中耗费的时间,并且包含了函数调用的其他函数内部花费的时间。第二个percall列列出了对函数的每次调用的平均时间,包括其调用的函数耗费的时间。

这种输出信息要比timeit模块的原始计时信息富有启发意义的多。我们立即可以发现,function_b()与function_c()使用了被调用5000次以上的生成器,使得它们的速度至少要比function_a()慢10倍以上。并且,function_b()调用了更多通常意义上的函数,包括调用内置的sorted()函数,这使得其几乎比function_c()还要慢两倍。当然,timeit() 模块提供了足够的信息来查看计时上存在的这些差别,但cProfile模块允许我们了解为什么会存在这些差别。正如timeit模块允许对代码进行计时而又不需要对其监控一样,cProfile模块也可以做到这一点。然而,从命令行使用cProfile模块时,我们不能精确地指定要执行的 是什么——而只是执行给定的程序或模块,并报告所有这些的计时结果。需要使用的 命令行是python3 -m cProfile ,产生的输出信息与前面看到的一 样,下面给出的是输出信息样例,格式上进行了一些调整,并忽略了大多数行:

function calls ( primitive calls) in 37.718 CPU secs

ncalls tottime percall cumtime percall filename:lineno(function)

10.000 0.000 37.718 37.718:1 ( )

10.719 0.719 37.717 37.717:12( )

1000 1.569 0.002 1.569 0.002:20(function_a)

1000 0.011 0.000 22.560 0.023:27(function_b)

7.078 0.000 7.078 0.000:28( )

1000 6.510 0.007 12.825 0.013:35(function_c)

6.316 0.000 6.316 0.000:36( )

在cProfile术语学中,原始调用指的就是非递归的函数调用。

以这种方式使用cProfile模块对于识别值得进一步研究的区域是有用的。比如,这里 我们可以清晰地看到function_b()需要耗费更长的时间,但是我们怎样获取进一步的详细资料?我们可以使用(function_b())来替换对function_b()的调用。或者可以保存完全的profile数据并使用pstats模块对其进行分析。要保存profile,就必须对命令行进行稍许修改:python3 -m cProfile -o profileDataFile 。 之后可以对 profile 数据进行分析,比如启动IDLE,导入pstats模块,赋予其已保存的profileDataFile,或者也可以在控制台中交互式地使用pstats。

下面给出的是一个非常短的控制台会话实例,为使其适合页面展示,进行了适当调整,我们自己的输入则以粗体展示:

$ python3 -m cProfile -o

$ python3 -m pstats

Welcome to the profile statistics browser.

% callers function_b

Random listing order was used

List reduced from 44 to 1 due to restriction

Function was called by...

ncalls tottime cumtime

:27(function_b) <- 1000 0.011 22.251:12( )

% callees function_b

Random listing order was used

List reduced from 44 to 1 due to restriction

Function called...

ncalls tottime cumtime

:27(function_b)->

1000 0.005 0.005 built-in method bisectJeft

1000 0.001 0.001 built-in method len

1000 1 5.297 22.234 built-in method sorted

输入help可以获取命令列表,help后面跟随命令名可以获取该命令的更多信息。比如, help stats将列出可以赋予stats命令的参数。还有其他一些可用的工具,可以提供profile数据的图形化展示形式,比如 RunSnakeRun (/prograinming/runsnakerun), 该工具需要依赖于wxPython GUI库。

使用timeit与cProfile模块,我们可以识别出我们自己代码中哪些区域会耗费超过预期的时间;使用cProfile模块,还可以准确算岀时间消耗在哪里。

以上内容部分摘自视频课程 05后端编程Python-19调试、测试和性能调优(下) ,更多实操示例请参照视频讲解。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领。

免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。

标签: Python

“模块调用相关剖析-Python脚本促成OC代码重构通常 (模块的调用关系)” 的相关文章

b-b-个入门建议!-Python-技术书籍推荐-附赠-11 (b+b+b等于什么)

b-b-个入门建议!-Python-技术书籍推荐-附赠-11 (b+b+b等于什么)

近年来,Python 持续火爆,越来越多的人开始入门学习 Python。RealPython 作为最受好评的 Python 学习网站,拥有超百万的浏览量,以下是 RealPython 的开发者给...

处置日常义务的终极工具!-Python-文件读写实战 (处置行为是什么意思)

处置日常义务的终极工具!-Python-文件读写实战 (处置行为是什么意思)

/target=_blankclass=infotextkey>Python文件的读写操作时,有很多须要思考的细节,这包含文件关上形式、读取和写入数据的方法、意外处置等。 在本文中,...

Python中的Random模块-摸索随机性的神奇环球 (python编程)

Python中的Random模块-摸索随机性的神奇环球 (python编程)

随机性在计算机编程和数据迷信中表演着至关关键的角色。/target=_blankclass=infotextkey>Python中的random模块提供了丰盛的工具和函数,协助咱们生成随机数...

惰性求值和lambda表达式的强大组合-Python高级技巧 (惰性求值和逻辑短路)

惰性求值和lambda表达式的强大组合-Python高级技巧 (惰性求值和逻辑短路)

Lambda 表达式 在 Python 中,Lambda 表达式是一个匿名函数,它可以在需要函数对象的地方使用。Lambda 表达式的语法如下: lambda arguments: exp...

一份收藏者必备清单-100个精选Python库 (收藏者的心态)

一份收藏者必备清单-100个精选Python库 (收藏者的心态)

/target=_blankclass=infotextkey>Python为啥这么火,这么多人学,就是由于繁难好学,性能弱小,整个社区十分生动,资料很多。而且这言语触及了方方面面,比如智能...

掌握网络世界的无限可能-Python分布式爬虫助力搜索引擎打造 (掌握网络世界的好处)

掌握网络世界的无限可能-Python分布式爬虫助力搜索引擎打造 (掌握网络世界的好处)

主从模式 主从模式是一种简单的分布式爬虫架构,其中一台主机作为控制节点,负责管理所有运行爬虫的从机。 主节点负责向从机分配任务,并接收新生成的任务。从机只需要从主节点接收任务,并把新生...

轻松把握多线程和多进程-Python编程进阶 (多线是什么意思)

轻松把握多线程和多进程-Python编程进阶 (多线是什么意思)

1、简介 咱们将讨论如何应用/target=_blankclass=infotextkey>Python口头多线程和多进程义务。它们提供了在单个进程或多个进程之间口头并发操作的方法。并...

生成-UUID-操作-Python-齐全指南-格局和经常出现疑问 (生成uuid java)

生成-UUID-操作-Python-齐全指南-格局和经常出现疑问 (生成uuid java)

UUID(UniversallyUniqueIdentifier,通用惟一标识符)是一种全局惟一标识符生成形式,用于创立举世无双的标识符。/target=_blankclass=infotextk...