Python已经演化出了一个普遍的生态系统,该生态系统可以让Python程序员的生活变得更加简单,减小他们重复造轮的工做。一样的理念也适用于工具开发者的工做,即使他们开发出的工具并无出如今最终的程序中。本文将介绍Python程序员必知必会的开发者工具。html
对于开发者来讲,最实用的帮助莫过于帮助他们编写代码文档了。pydoc模块能够根据源代码中的docstrings为任何可导入模块生成格式良好的文档。Python包含了两个测试框架来自动测试代码以及验证代码的正确性:1)doctest模块,该模块能够从源代码或独立文件的例子中抽取出测试用例。2)unittest模块,该模块是一个全功能的自动化测试框架,该框架提供了对测试准备(test fixtures), 预约义测试集(predefined test suite)以及测试发现(test discovery)的支持。python
trace模块能够监控Python执行程序的方式,同时生成一个报表来显示程序的每一行执行的次数。这些信息能够用来发现未被自动化测试集所覆盖的程序执行路径,也能够用来研究程序调用图,进而发现模块之间的依赖关系。编写并执行测试能够发现绝大多数程序中的问题,Python使得debug工做变得更加简单,这是由于在大部分状况下,Python都可以将未被处理的错误打印到控制台中,咱们称这些错误信息为traceback。若是程序不是在文本控制台中运行的,traceback也可以将错误信息输出到日志文件或是消息对话框中。当标准的traceback没法提供足够的信息时,可使用cgitb 模块来查看各级栈和源代码上下文中的详细信息,好比局部变量。cgitb模块还可以将这些跟踪信息以HTML的形式输出,用来报告web应用中的错误。git
一旦发现了问题出在哪里后,就须要使用到交互式调试器进入到代码中进行调试工做了,pdb模块可以很好地胜任这项工做。该模块能够显示出程序在错误产生时的执行路径,同时能够动态地调整对象和代码进行调试。当程序经过测试并调试后,下一步就是要将注意力放到性能上了。开发者可使用profile以及 timit 模块来测试程序的速度,找出程序中究竟是哪里很慢,进而对这部分代码独立出来进行调优的工做。Python程序是经过解释器执行的,解释器的输入是原有程序的字节码编译版本。这个字节码编译版本能够在程序执行时动态地生成,也能够在程序打包的时候就生成。compileall 模块能够处理程序打包的事宜,它暴露出了打包相关的接口,该接口可以被安装程序和打包工具用来生成包含模块字节码的文件。同时,在开发环境中,compileall模块也能够用来验证源文件是否包含了语法错误。程序员
在源代码级别,pyclbr 模块提供了一个类查看器,方便文本编辑器或是其余程序对Python程序中有意思的字符进行扫描,好比函数或者是类。在提供了类查看器之后,就无需引入代码,这样就避免了潜在的反作用影响。web
doctest
模块若是函数,类或者是模块的第一行是一个字符串,那么这个字符串就是一个文档字符串。能够认为包含文档字符串是一个良好的编程习惯,这是由于这些字符串能够给Python程序开发工具提供一些信息。好比,help()
命令可以检测文档字符串,Python相关的IDE也可以进行检测文档字符串的工做。因为程序员倾向于在交互式shell中查看文档字符串,因此最好将这些字符串写的简短一些。例如算法
# mult.py class Test: """ >>> a=Test(5) >>> a.multiply_by_2() 10 """ def __init__(self, number): self._number=number def multiply_by_2(self): return self._number*2
在编写文档时,一个常见的问题就是如何保持文档和实际代码的同步。例如,程序员也许会修改函数的实现,可是却忘记了更新文档。针对这个问题,咱们可使用doctest模块。doctest模块收集文档字符串,并对它们进行扫描,而后将它们做为测试进行执行。为了使用doctest模块,咱们一般会新建一个用于测试的独立的模块。例如,若是前面的例子Test class
包含在文件mult.py
中,那么,你应该新建一个testmult.py
文件用来测试,以下所示:shell
# testmult.py import mult, doctest doctest.testmod(mult, verbose=True) # Trying: # a=Test(5) # Expecting nothing # ok # Trying: # a.multiply_by_2() # Expecting: # 10 # ok # 3 items had no tests: # mult # mult.Test.__init__ # mult.Test.multiply_by_2 # 1 items passed all tests: # 2 tests in mult.Test # 2 tests in 4 items. # 2 passed and 0 failed. # Test passed.
在这段代码中,doctest.testmod(module)
会执行特定模块的测试,而且返回测试失败的个数以及测试的总数目。若是全部的测试都经过了,那么不会产生任何输出。不然的话,你将会看到一个失败报告,用来显示指望值和实际值之间的差异。若是你想看到测试的详细输出,你可使用testmod(module, verbose=True)
.django
若是不想新建一个单独的测试文件的话,那么另外一种选择就是在文件末尾包含相应的测试代码:编程
if __name__ == '__main__': import doctest doctest.testmod()
若是想执行这类测试的话,咱们能够经过-m选项调用doctest模块。一般来说,当执行测试的时候没有任何的输出。若是想查看详细信息的话,能够加上-v选项。安全
$ python -m doctest -v mult.py
unittest
模块若是想更加完全地对程序进行测试,咱们可使用unittest模块。经过单元测试,开发者能够为构成程序的每个元素(例如,独立的函数,方法,类以及模块)编写一系列独立的测试用例。当测试更大的程序时,这些测试就能够做为基石来验证程序的正确性。当咱们的程序变得愈来愈大的时候,对不一样构件的单元测试就能够组合起来成为更大的测试框架以及测试工具。这可以极大地简化软件测试的工做,为找到并解决软件问题提供了便利。
# splitter.py import unittest def split(line, types=None, delimiter=None): """Splits a line of text and optionally performs type conversion. ... """ fields = line.split(delimiter) if types: fields = [ ty(val) for ty,val in zip(types,fields) ] return fields class TestSplitFunction(unittest.TestCase): def setUp(self): # Perform set up actions (if any) pass def tearDown(self): # Perform clean-up actions (if any) pass def testsimplestring(self): r = split('GOOG 100 490.50') self.assertEqual(r,['GOOG','100','490.50']) def testtypeconvert(self): r = split('GOOG 100 490.50',[str, int, float]) self.assertEqual(r,['GOOG', 100, 490.5]) def testdelimiter(self): r = split('GOOG,100,490.50',delimiter=',') self.assertEqual(r,['GOOG','100','490.50']) # Run the unittests if __name__ == '__main__': unittest.main() #... #---------------------------------------------------------------------- #Ran 3 tests in 0.001s #OK
在使用单元测试时,咱们须要定义一个继承自unittest.TestCase
的类。在这个类里面,每个测试都以方法的形式进行定义,并都以test
打头进行命名——例如,’testsimplestring‘
,’testtypeconvert‘
以及相似的命名方式(有必要强调一下,只要方法名以test
打头,那么不管怎么命名都是能够的)。在每一个测试中,断言能够用来对不一样的条件进行检查。
实际的例子:
假如你在程序里有一个方法,这个方法的输出指向标准输出(sys.stdout)。这一般意味着是往屏幕上输出文本信息。若是你想对你的代码进行测试来证实这一点,只要给出相应的输入,那么对应的输出就会被显示出来。
# url.py def urlprint(protocol, host, domain): url = '{}://{}.{}'.format(protocol, host, domain) print(url)
内置的print函数在默认状况下会往sys.stdout发送输出。为了测试输出已经实际到达,你可使用一个替身对象对其进行模拟,而且对程序的指望值进行断言。unittest.mock
模块中的patch()
方法能够只在运行测试的上下文中才替换对象,在测试完成后就马上返回对象原始的状态。下面是urlprint()
方法的测试代码:
#urltest.py from io import StringIO from unittest import TestCase from unittest.mock import patch import url class TestURLPrint(TestCase): def test_url_gets_to_stdout(self): protocol = 'http' host = 'www' domain = 'example.com' expected_url = '{}://{}.{}\n'.format(protocol, host, domain) with patch('sys.stdout', new=StringIO()) as fake_out: url.urlprint(protocol, host, domain) self.assertEqual(fake_out.getvalue(), expected_url)
urlprint()
函数有三个参数,测试代码首先给每一个参数赋了一个假值。变量expected_url
包含了指望的输出字符串。为了可以执行测试,咱们使用了unittest.mock.patch()
方法做为上下文管理器,把标准输出sys.stdout
替换为了StringIO对象,这样发送的标准输出的内容就会被StringIO对象所接收。变量fake_out
就是在这一过程当中所建立出的模拟对象,该对象可以在with
所处的代码块中所使用,来进行一系列的测试检查。当with语句完成时,patch方法可以将全部的东西都复原到测试执行以前的状态,就好像测试没有执行同样,而这无需任何额外的工做。但对于某些Python的C扩展来说,这个例子却显得毫无心义,这是由于这些C扩展程序绕过了sys.stdout的设置,直接将输出发送到了标准输出上。这个例子仅适用于纯Python代码的程序(若是你想捕获到相似C扩展的输入输出,那么你能够经过打开一个临时文件而后将标准输出重定向到该文件的技巧来进行实现)。
pdb
模块Python在pdb模块中包含了一个简单的基于命令行的调试器。pdb模块支持过后调试(post-mortem debugging),栈帧探查(inspection of stack frames),断点(breakpoints),单步调试(single-stepping of source lines)以及代码审查(code evaluation)。
有好几个函数
都可以在程序中调用调试器,或是在交互式的Python终端中进行调试工做。
在全部启动调试器的函数中,函数set_trace()
也许是最简易实用的了。若是在复杂程序中发现了问题,能够在代码中插入set_trace()
函数,并运行程序。当执行到set_trace()函数时,这就会暂停程序的执行并直接跳转到调试器中,这时候你就能够大展手脚开始检查运行时环境了。当退出调试器时,调试器会自动恢复程序的执行。
假设你的程序有问题,你想找到一个简单的方法来对它进行调试。
若是你的程序崩溃时报了一个异常错误,那么你能够用python3 -i someprogram.py
这个命令来运行你的程序,这可以很好地发现问题所在。-i选项代表只要程序终结就当即启动一个交互式shell。在这个交互式shell中,你就能够很好地探查到底发生了什么致使程序的错误。例如,若是你有如下代码:
def function(n): return n + 10 function("Hello")
若是使用python3 -i
命令运行程序就会产生以下输出:
python3 -i sample.py Traceback (most recent call last): File "sample.py", line 4, in <module> function("Hello") File "sample.py", line 2, in function return n + 10 TypeError: Can't convert 'int' object to str implicitly >>> function(20) 30 >>>
若是你没有发现什么明显的错误,那么你能够进一步地启动Python调试器。例如:
>>> import pdb >>> pdb.pm() > sample.py(4)func() -> return n + 10 (Pdb) w sample.py(6)<module>() -> func('Hello') > sample.py(4)func() -> return n + 10 (Pdb) print n 'Hello' (Pdb) q >>>
若是你的代码身处的环境很难启动一个交互式shell的话(好比在服务器环境下),你能够增长错误处理的代码,并本身输出跟踪信息。例如:
import traceback import sys try: func(arg) except: print('**** AN ERROR OCCURRED ****') traceback.print_exc(file=sys.stderr)
若是你的程序并无崩溃,而是说程序的行为与你的预期表现的不一致,那么你能够尝试在一些可能出错的地方加入print()
函数。若是你打算采用这种方案的话,那么还有些相关的技巧值得探究。首先,函数traceback.print_stack()
可以在被执行时当即打印出程序中栈的跟踪信息。例如:
>>> def sample(n): ... if n > 0: ... sample(n-1) ... else: ... traceback.print_stack(file=sys.stderr) ... >>> sample(5) File "<stdin>", line 1, in <module> File "<stdin>", line 3, in sample File "<stdin>", line 3, in sample File "<stdin>", line 3, in sample File "<stdin>", line 3, in sample File "<stdin>", line 3, in sample File "<stdin>", line 5, in sample >>>
另外,你能够在程序中任意一处使用pdb.set_trace()
手动地启动调试器,就像这样:
import pdb def func(arg): ... pdb.set_trace() ...
在深刻解析大型程序的时候,这是一个很是实用的技巧,这样操做可以清楚地了解程序的控制流或是函数的参数。好比,一旦调试器启动了以后,你就可使用print或者w命令来查看变量,来了解栈的跟踪信息。
在进行软件调试时,千万不要让事情变得很复杂。有时候仅仅须要知道程序的跟踪信息就可以解决大部分的简单错误(好比,实际的错误老是显示在跟踪信息的最后一行)。在实际的开发过程当中,将print()函数插入到代码中也可以很方便地显示调试信息(只须要记得在调试完之后将print语句删除掉就好了)。调试器的通用用法是在崩溃的函数中探查变量的值,知道如何在程序崩溃之后再进入到调试器中就显得很是实用。在程序的控制流不是那么清楚的状况下,你能够插入pdb.set_trace()语句来理清复杂程序的思路。本质上,程序会一直执行直到遇到set_trace()调用,以后程序就会马上跳转进入到调试器中。在调试器里,你就能够进行更多的尝试。若是你正在使用Python的IDE,那么IDE一般会提供基于pdb的调试接口,你能够查阅IDE的相关文档来获取更多的信息。
下面是一些Python调试器入门的资源列表:
profile模块和cProfile模块能够用来分析程序。它们的工做原理都同样,惟一的区别是,cProfile模块是以C扩展的方式实现的,如此一来运行的速度也快了不少,也显得比较流行。这两个模块均可以用来收集覆盖信息(好比,有多少函数被执行了),也可以收集性能数据。对一个程序进行分析的最简单的方法就是运行这个命令:
% python -m cProfile someprogram.py
此外,也可使用profile模块中的run函数:
run(command [, filename])
该函数会使用exec
语句执行command中的内容。filename是可选的文件保存名,若是没有filename的话,该命令的输出会直接发送到标准输出上。
下面是分析器执行完成时的输出报告:
126 function calls (6 primitive calls) in 5.130 CPU seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.030 0.030 5.070 5.070 <string>:1(?) 121/1 5.020 0.041 5.020 5.020 book.py:11(process) 1 0.020 0.020 5.040 5.040 book.py:5(?) 2 0.000 0.000 0.000 0.000 exceptions.py:101(_ _init_ _) 1 0.060 0.060 5.130 5.130 profile:0(execfile('book.py')) 0 0.000 0.000 profile:0(profiler)
当输出中的第一列包含了两个数字时(好比,121/1),后者是元调用(primitive call)的次数,前者是实际调用的次数(译者注:只有在递归状况下,实际调用的次数才会大于元调用的次数,其余状况下二者都相等)。对于绝大部分的应用程序来说使用该模块所产生的的分析报告就已经足够了,好比,你只是想简单地看一下你的程序花费了多少时间。而后,若是你还想将这些数据保存下来,并在未来对其进行分析,你可使用pstats
模块。
假设你想知道你的程序究竟在哪里花费了多少时间。
若是你只是想简单地给你的整个程序计时的话,使用Unix中的time命令就已经彻底可以应付了。例如:
bash % time python3 someprogram.py real 0m13.937s user 0m12.162s sys 0m0.098s bash %
一般来说,分析代码的程度会介于这两个极端之间。好比,你可能已经知道你的代码会在一些特定的函数中花的时间特别多。针对这类特定函数的分析,咱们可使用修饰器decorator,例如:
import time from functools import wraps def timethis(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() r = func(*args, **kwargs) end = time.perf_counter() print('{}.{} : {}'.format(func.__module__, func.__name__, end - start)) return r return wrapper
使用decorator的方式很简单,你只须要把它放在你想要分析的函数的定义前面就能够了。例如:
>>> @timethis ... def countdown(n): ... while n > 0: ... n -= 1 ... >>> countdown(10000000) __main__.countdown : 0.803001880645752 >>>
若是想要分析一个语句块的话,你能够定义一个上下文管理器(context manager)。例如:
import time from contextlib import contextmanager @contextmanager def timeblock(label): start = time.perf_counter() try: yield finally: end = time.perf_counter() print('{} : {}'.format(label, end - start))
接下来是如何使用上下文管理器的例子:
>>> with timeblock('counting'): ... n = 10000000 ... while n > 0: ... n -= 1 ... counting : 1.5551159381866455 >>>
若是想研究一小段代码的性能的话,timeit模块会很是有用。例如:
>>> from timeit import timeit >>> timeit('math.sqrt(2)', 'import math') 0.1432319980012835 >>> timeit('sqrt(2)', 'from math import sqrt') 0.10836604500218527 >>>
timeit的工做原理是,将第一个参数中的语句执行100万次,而后计算所花费的时间。第二个参数指定了一些测试以前须要作的环境准备工做。若是你须要改变迭代的次数,能够附加一个number参数,就像这样:
>>> timeit('math.sqrt(2)', 'import math', number=10000000) 1.434852126003534 >>> timeit('sqrt(2)', 'from math import sqrt', number=10000000) 1.0270336690009572 >>>
当进行性能评估的时候,要牢记任何得出的结果只是一个估算值。函数time.perf_counter()
可以在任一平台提供最高精度的计时器。然而,它也只是记录了天然时间,记录天然时间会被不少其余因素影响,好比,计算机的负载。若是你对处理时间而非天然时间感兴趣的话,你可使用time.process_time()
。例如:
import time from functools import wraps def timethis(func): @wraps(func) def wrapper(*args, **kwargs): start = time.process_time() r = func(*args, **kwargs) end = time.process_time() print('{}.{} : {}'.format(func.__module__, func.__name__, end - start)) return r return wrapper
最后也是至关重要的就是,若是你想作一个详细的性能评估的话,你最好查阅time,timeit以及其余相关模块的文档,这样你才可以对平台相关的不一样之处有所了解。
profile模块中最基础的东西就是run()函数了。该函数会把一个语句字符串做为参数,而后在执行语句时生成所花费的时间报告。
import profile def fib(n): # from literateprograms.org # http://bit.ly/hlOQ5m if n == 0: return 0 elif n == 1: return 1 else: return fib(n-1) + fib(n-2) def fib_seq(n): seq = [] if n > 0: seq.extend(fib_seq(n-1)) seq.append(fib(n)) return seq profile.run('print(fib_seq(20)); print')
当你的程序运行地很慢的时候,你就会想去提高它的运行速度,可是你又不想去借用一些复杂方案的帮助,好比使用C扩展或是just-in-time(JIT)编译器。
那么这时候应该怎么办呢?要牢记性能优化的第一要义就是“不要为了优化而去优化,应该在咱们开始写代码以前就想好应该怎样编写高性能的代码”。第二要义就是“优化必定要抓住重点,找到程序中最重要的地方去优化,而不要去优化那些不重要的部分”。
一般来说,你会发现你的程序在某些热点上花费了不少时间,好比内部数据的循环处理。一旦你发现了问题所在,你就能够对症下药,让你的程序更快地执行。
许多开发者刚开始的时候会将Python做为一个编写简单脚本的工具。当编写脚本的时候,很容易就会写一些没有结构的代码出来。例如:
import sys import csv with open(sys.argv[1]) as f: for row in csv.reader(f): # Some kind of processing
可是,却不多有人知道,定义在全局范围内的代码要比定义在函数中的代码执行地慢。他们之间速度的差异是由于局部变量与全局变量不一样的实现所引发的(局部变量的操做要比全局变量来得快)。因此,若是你想要让程序更快地运行,那么你能够简单地将代码放在一个函数中,就像这样:
import sys import csv def main(filename): with open(filename) as f: for row in csv.reader(f): # Some kind of processing ... main(sys.argv[1])
这样操做之后,处理速度会有提高,可是这个提高的程度依赖于程序的复杂性。根据经验来说,一般都会提高15%到30%之间。
当使用点(.)
操做符去访问属性时都会带来必定的消耗。本质上来说,这会触发一些特殊方法的执行,好比__getattribute__()
和__getattr__()
,这一般都会致使去内存中字典数据的查询。
你能够经过两种方式来避免属性的访问,第一种是使用from module import name的方式。第二种是将对象的方法名保存下来,在调用时直接使用。为了解释地更加清楚,咱们来看一个例子:
import math def compute_roots(nums): result = [] for n in nums: result.append(math.sqrt(n)) return result # Test nums = range(1000000) for n in range(100): r = compute_roots(nums)
上面的代码在个人计算机上运行大概须要40秒的时间。如今咱们把上面代码中的compute_roots()
函数改写一下:
from math import sqrt def compute_roots(nums): result = [] result_append = result.append for n in nums: result_append(sqrt(n)) return result nums = range(1000000) for n in range(100): r = compute_roots(nums)
这个版本的代码执行一下大概须要29秒。这两个版本的代码惟一的不一样之处在于后面一个版本减小了对属性的访问。在后面一段代码中,咱们使用了sqrt()
方法,而非math.sqrt()
。result.append()
函数也被存进了一个局部变量result_append
中,而后在循环当中重复使用。
然而,有必要强调一点是说,这种方式的优化仅仅针对常常运行的代码有效,好比循环。因而可知,优化仅仅在那些当心挑选出来的地方才会真正获得体现。
上面已经讲过,局部变量的操做比全局变量来得快。对于常常要访问的变量来讲,最好把他们保存成局部变量。例如,考虑刚才已经讨论过的compute_roots()函数修改版:
import math def compute_roots(nums): sqrt = math.sqrt result = [] result_append = result.append for n in nums: result_append(sqrt(n)) return result
在这个版本中,sqrt
函数被一个局部变量所替代。若是你执行这段代码的话,大概须要25秒就执行完了(前一个版本须要29秒)。 此次速度的提高是由于sqrt局部变量的查询比sqrt函数的全局查询来得稍快。
局部性原来一样适用于类的参数。一般来说,使用self.name
要比直接访问局部变量来得慢。在内部循环中,咱们能够将常常要访问的属性保存为一个局部变量。例如:
#Slower class SomeClass: ... def method(self): for x in s: op(self.value) # Faster class SomeClass: ... def method(self): value = self.value for x in s: op(value)
任什么时候候当你想给你的代码添加其余处理逻辑,好比添加装饰器,属性或是描述符,你都是在拖慢你的程序。例如,考虑这样一个类:
class A: def __init__(self, x, y): self.x = x self.y = y @property def y(self): return self._y @y.setter def y(self, value): self._y = value
如今,让咱们简单地测试一下:
>>> from timeit import timeit >>> a = A(1,2) >>> timeit('a.x', 'from __main__ import a') 0.07817923510447145 >>> timeit('a.y', 'from __main__ import a') 0.35766440676525235 >>>
正如你所看到的,咱们访问属性y比访问简单属性x不是慢了一点点,整整慢了4.5倍之多。若是你在意性能的话,你就颇有必要问一下你本身,对y的那些额外的定义是否都是必要的了。若是不是的话,那么你应该把那些额外的定义删掉,用一个简单的属性就够了。若是只是由于在其余语言里面常用getter和setter函数的话,你彻底没有必要在Python中也使用相同的编码风格。
内置的数据结构,例如字符串(string),元组(tuple),列表(list),集合(set)以及字典(dict)都是用C语言实现的,正是由于采用了C来实现,因此它们的性能表现也很好。若是你倾向于使用你本身的数据结构做为替代的话(例如,链表,平衡树或是其余数据结构),想达到内置数据结构的速度的话是很是困难的。所以,你应该尽量地使用内置的数据结构。
有时候程序员会有点儿走神,在不应用到数据结构的地方去用数据结构。例如,有人可能会写这样的的代码:
values = [x for x in sequence] squares = [x*x for x in values]
也许他这么写是为了先获得一个列表,而后再在这个列表上进行一些操做。可是第一个列表是彻底没有必要写在这里的。咱们能够简单地把代码写成这样就好了:
squares = [x*x for x in sequence]
有鉴于此,你要当心那些偏执程序员所写的代码了,这些程序员对Python的值共享机制很是偏执。函数copy.deepcopy()
的滥用也许是一个信号,代表该代码是由菜鸟或者是不相信Python内存模型的人所编写的。在这样的代码里,减小copy的使用也许会比较安全。
在优化以前,颇有必要先详细了解一下你所要使用的算法。若是你可以将算法的复杂度从O(n^2)
降为O(n log n)
的话,程序的性能将获得极大的提升。
若是你已经打算进行优化工做了,那就颇有必要全局地考虑一下。普适的原则就是,不要想去优化程序的每个部分,这是由于优化工做会让代码变得晦涩难懂。相反,你应该把注意力集中在已知的性能瓶颈处,例如内部循环。
你须要谨慎地对待微优化(micro-optimization)的结果。例如,考虑下面两种建立字典结构的方式:
a = { 'name' : 'AAPL', 'shares' : 100, 'price' : 534.22 } b = dict(name='AAPL', shares=100, price=534.22)
后面那一种方式打字打的更少一些(由于你没必要将key的名字用双引号括起来)。然而当你将这两种编码方式进行性能对比时,你会发现使用dict()
函数的方式比另外一种慢了3倍之多!知道了这一点之后,你也许会倾向于扫描你的代码,把任何出现dict()
的地方替换为另外一种冗余的写法。然而,一个聪明的程序员绝对不会这么作,他只会将注意力放在值得关注的地方,好比在循环上。在其余地方,速度的差别并非最重要的。可是,若是你想让你的程序性能有质的飞跃的话,你能够去研究下基于JIT技术的工具。好比,PyPy项目,该项目是Python解释器的另外一种实现,它可以分析程序的执行并为常常执行的代码生成机器码,有时它甚至可以让Python程序的速度提高一个数量级,达到(甚至超过)C语言编写的代码的速度。可是不幸的是,在本文正在写的时候,PyPy尚未彻底支持Python 3。因此,咱们仍是在未来再来看它到底会发展的怎么样。基于JIT技术的还有Numba项目。该项目实现的是一个动态的编译器,你能够将你想要优化的Python函数以注解的方式进行标记,而后这些代码就会在LLVM的帮助下被编译成机器码。该项目也可以带来极大的性能上的提高。然而,就像PyPy同样,该项目对Python 3的支持还只是实验性的。
最后,可是也很重要的是,请牢记John Ousterhout(译者注:Tcl和Tk的发明者,现为斯坦福大学计算机系的教授)说过的话“将不工做的东西变成可以工做的,这才是最大的性能提高”。在你须要优化前不要过度地考虑程序的优化工做。程序的正确性一般来说都比程序的性能要来的重要。
--
原文:Developer Tools in Python
转载自:伯乐在线 - brightconan