《Python高性能编程》——2.13 在优化期间进行单元测试保持代码的正确性

2.13 在优化期间进行单元测试保持代码的正确性

如果你不对你的代码进行单元测试,那么从长远来看你可能正在损害你的生产力。Ian(脸红)十分尴尬地提到有一次他花了一整天的时间优化他的代码,因为嫌麻烦所以他禁用了单元测试,最后却发现那个显著的速度提升只是因为他破坏了需要优化的那段算法。这样的错误你一次都不要犯。

除了单元测试,你还应该坚定地考虑使用coverage.py。它会检查有哪些代码行被你的测试所覆盖并找出那些没有被覆盖的代码。这可以让你迅速知道你是否测试了你想要优化的代码,那么在优化过程中可能潜伏的任何错误都会被迅速抓出来。

No-op的@profile修饰器

如果你的代码使用了line_profiler或者memory_profiler的@profile修饰器,那么你的单元测试会引发一个NameError异常并失败。原因是单元测试框架不会将@profile修饰器注入本地名字空间。no-op修饰器可以在这种时候解决问题。在你测试时把它加入你的代码块,并在你结束测试后移除它是在方便不过的事情了。

使用no-op修饰器,你可以运行你的测试而不需要修改你的代码。这意味着你可以在每次优化之后都运行你的测试,你将永远不会倒在一个出问题的优化步骤上。

如例2-20所示,假设我们有一个ex.py模块,它有一个测试用例(基于nosetests框架)和一个函数,这个函数我们正在用line_profiler或者memory_profiler进行性能分析。

例2-20 一个简单的函数和一个测试用例需要用到@profile

# ex.py
import unittest

@profile
def some_fn(nbr):
    return nbr * 2

class TestCase(unittest.TestCase):
    def test(self):
        result = some_fn(2)
        self.assertEquals(result, 4)

如果我们运行nosetests测试我们的代码就会得到一个NameError:

$ nosetests ex.py
E
======================================================================
ERROR: Failure: NameError (name 'profile' is not defined)
...
NameError: name 'profile' is not defined
Ran 1 test in 0.001s

FAILED (errors=1)

解决方法是在ex.py开头添加一个no-op修饰器(你可以在完成性能分析之后移除它)。如果在名字空间中寻找不到@profile修饰器(因为没有使用line_profiler或者memory_profiler),那么我们写的no-op版本的修饰器就会被加入名字空间。如果line_profiler或者memory_profiler已经将新的函数加入名字空间,那么我们no-op版本的修饰器就会被忽略。

对于line_profiler,我们可以加入例2-21的代码。

例2-21 在单元测试时在名字空间中加入针对line_profiler的no-op@profile修饰器

# line_profiler
if '__builtin__' not in dir() or not hasattr(__builtin__, 'profile'):
    def profile(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs)
        return inner

__builtin__检查是针对nosetests的,hasattr则用来检查@profile修饰器是否已经被加入名字空间。现在可以在我们的代码上成功运行nosetests了:

$ kernprof.py -v -l ex.py
Line #      Hits        Time   Per %%HTMLit   % Time   Line Contents
==============================================================
    11                                          @profile
    12                                          def some_fn(nbr):
    13         1           3       3.0    100.0     return nbr * 2

$ nosetests ex.py
.
Ran 1 test in 0.000s

对于memory_profiler,我们使用例2-22的代码。

例2-22 在单元测试时在名字空间中加入针对memory_profiler的no-op@profile修饰器

# memory_profiler
if 'profile' not in dir():
    def profile(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs)
        return inner

期望产生的输出如下:

python -m memory_profiler ex.py
...
Line #    Mem usage    Increment   Line Contents
================================================
    11   10.809 MiB    0.000 MiB   @profile
    12                             def some_fn(nbr):
    13   10.809 MiB    0.000 MiB       return nbr * 2

$ nosetests ex.py
.
Ran 1 test in 0.000

不使用这些修饰器可以节省你几分钟,但是一旦你在一个破坏你代码的错误优化上失去了好几个小时,你就会想要把这个加入你的工作流程了。

时间: 2024-10-23 10:43:53

《Python高性能编程》——2.13 在优化期间进行单元测试保持代码的正确性的相关文章

《Python高性能编程》——导读

前 言 Python很容易学.你之所以阅读本书可能是因为你的代码现在能够正确运行,而你希望它能跑得更快.你可以很轻松地修改代码,反复地实现你的想法,你对这一点很满意.但能够轻松实现和代码跑得够快之间的取舍却是一个世人皆知且令人惋惜的现象.而这个问题其实是可以解决的. 有些人想要让顺序执行的过程跑得更快.有些人需要利用多核架构.集群,或者图形处理单元的优势来解决他们的问题.有些人需要可伸缩系统在保证可靠性的前提下酌情或根据资金多少处理更多或更少的工作.有些人意识到他们的编程技巧,通常是来自其他语言

《Python高性能编程》——第1章 理解高性能Python 1.1 基本的计算机系统

第1章 理解高性能Python 读完本章之后你将能够回答下列问题 计算机架构有哪些元素? 常见的计算机架构有哪些? 计算机架构在Python中的抽象表达是什么? 实现高性能Python代码的障碍在哪里? 性能问题有哪些种类? 计算机编程可以被认为是以特定的方式进行数据的移动和转换来得到某种结果.然而这些操作有时间上的开销.因此,高性能编程可以被认为是通过降低开销(比如撰写更高效的代码)或改变操作方式(比如寻找一种更合适的算法)来让这些操作的代价最小化. 数据的移动发生在实际的硬件上,我们可以通过

《Python高性能编程》——1.2 将基本的元素组装到一起

1.2 将基本的元素组装到一起 仅理解计算机的基本组成部分并不足以理解高性能编程的问题.所有这些组件的互动与合作还会引入新的复杂度.本段将研究一些样本问题,描述理想的解决方案以及Python如何实现它们. 警告:本段可能看上去让人绝望--大多数问题似乎都证明Python并不适合解决性能问题.这不是真的,原因有两点.首先,在所有这些"高性能计算要素"中,我们忽视了一个至关重要的要素:开发者.原生Python在性能上欠缺的功能会被迅速开发出来.另外,我们会在本书中介绍各种模块和原理来帮助减

《Python高性能编程》——1.3 为什么使用Python

1.3 为什么使用Python Python具有高度的表现力且容易上手--新开发者会很快发现他们可以在很短时间里做到很多.许多Python库包含了用其他语言编写的工具,使Python可以轻易调用其他系统.比如,scikit-learn机器学习系统包含了LIBLINEAR和LIBSVM(两者皆以C语言写成),numpy库则包含了BLAS以及其他用C和Fortran语言写的库.因此,正确运用这些库的Python代码确实可以在速度上做到跟C媲美. Python被誉为"内含电池",因为它内建了

《Python高性能编程》——2.6 使用cProfile模块

2.6 使用cProfile模块 cProfile是一个标准库内建的分析工具.它钩入CPython的虚拟机来测量其每一个函数运行所花费的时间.这一技术会引入一个巨大的开销,但你会获得更多的信息.有时这些额外的信息会给你的代码带来令人惊讶的发现. cProfile是标准库内建的三个分析工具之一,另外两个是hotshot和profile.hotshot还处于实验阶段,profile则是原始的纯Python分析器.cProfile具有跟profile一样的接口,且是默认的分析工具.如果你对这些库的历史

《Python高性能编程》——第2章 通过性能分析找到瓶颈 2.1 高效地分析性能

第2章 通过性能分析找到瓶颈 读完本章之后你将能够回答下列问题 如何找到代码中速度和RAM的瓶颈? 如何分析CPU和内存使用情况? 我应该分析到什么深度? 如何分析一个长期运行的应用程序? 在CPython台面下发生了什么? 如何在调整性能的同时确保功能的正确? 性能分析帮助我们找到瓶颈,让我们在性能调优方面做到事半功倍.性能调优包括在速度上巨大的提升以及减少资源的占用,也就是说让你的代码能够跑得"足够快"以及"足够瘦".性能分析能够让你用最小的代价做出最实用的决定

《Python高性能编程》——2.8 用line_profiler进行逐行分析

2.8 用line_profiler进行逐行分析 根据Ian的观点,Robert Kern的line_profiler是调查Python的CPU密集型性能问题最强大的工具.它可以对函数进行逐行分析,你应该先用cProfile找到需要分析的函数,然后用line_profiler对函数进行分析. 当你修改你的代码时,值得打印出这个工具的输出以及代码的版本,这样你就拥有一个代码变化(无论有没有用)的记录,让你可以随时查阅.当你在进行逐行改变时,不要依赖你的记忆. 输入命令pip install lin

《Python高性能编程》——2.12 用dis模块检查CPython字节码

2.12 用dis模块检查CPython字节码 到目前为止我们已经展示了很多测量Python代码开销的方法(包括CPU和RAM的开销).不过,我们还没有看到在底层虚拟机的字节码层面发生的事情.了解"台面下"发生的事情有助于在脑海中对运行慢的函数建立一个模型,并能帮助你编译你的代码.所以现在让我们来看一些字节码. dis模块让我们能够看到基于栈的CPython虚拟机中运行的字节码.在你的Python代码运行的时候,了解虚拟机中发生了什么可以帮助你了解为什么某些编码风格会比其他的更快.同时

《Python高性能编程》——2.10 用heapy调查堆上的对象

2.10 用heapy调查堆上的对象 Guppy项目有一个内存堆的调查工具叫作heapy,可以让你查看Python堆中对象的数量以及每个对象的大小.当你需要知道某一时刻有多少对象被使用以及它们是否被垃圾收集时,你尤其需要这种深入解释器内部去了解内存中实际内容的能力.如果你受困于内存泄漏(可能由于你的对象的引用隐藏于一个复杂系统中),那么这个工具能帮你找到问题的关键点. 当你在审查你的代码,看它是否如你预期那样生成对象时,你会发现这个工具非常有用--结果很可能令你吃惊,并为你带来新的优化方向. 为