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

简介:

本节书摘来自异步社区《Python高性能编程》一书中的第2章,第2.13节,作者[美] 戈雷利克 (Micha Gorelick),胡世杰,徐旭彬 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

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

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

相关文章
|
6天前
|
Python
以下是一些常用的图表类型及其Python代码示例,使用Matplotlib和Seaborn库。
以下是一些常用的图表类型及其Python代码示例,使用Matplotlib和Seaborn库。
|
6天前
|
监控 测试技术 持续交付
软件测试中的性能瓶颈分析与优化策略
性能瓶颈,如同潜伏于软件深处的隐形障碍,悄然阻碍着系统的流畅运行。本文旨在揭示这些瓶颈的形成机理,剖析其背后的复杂成因,并汇聚一系列针对性的优化策略,为软件开发者提供一套系统性的解决方案。
|
7天前
|
Web App开发 JavaScript 前端开发
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
添加浮动按钮点击滚动到网页底部的纯JavaScript演示代码 IE9、11,Maxthon 1.6.7,Firefox30、31,360极速浏览器7.5.3.308下测试正常
|
1天前
|
数据安全/隐私保护 Python
探索Python中的装饰器:简化代码,提升效率
【9月更文挑战第32天】在Python编程世界中,装饰器是一个强大的工具,它允许我们在不改变函数源代码的情况下增加函数的功能。本文将通过直观的例子和代码片段,引导你理解装饰器的概念、使用方法及其背后的魔法,旨在帮助你写出更加优雅且高效的代码。
|
5天前
|
Python
? Python 装饰器入门:让代码更灵活和可维护
? Python 装饰器入门:让代码更灵活和可维护
11 4
|
5天前
|
缓存 测试技术 Python
探索Python中的装饰器:简化代码,提高可读性
【9月更文挑战第28天】在Python编程中,装饰器是一个强大的工具,它允许我们在不修改原有函数代码的情况下增加额外的功能。本文将深入探讨装饰器的概念、使用方法及其在实际项目中的应用,帮助读者理解并运用装饰器来优化和提升代码的效率与可读性。通过具体示例,我们将展示如何创建自定义装饰器以及如何利用它们简化日常的编程任务。
11 3
|
4天前
|
机器学习/深度学习 数据格式 Python
将特征向量转化为Python代码
将特征向量转化为Python代码
11 1
|
5天前
|
Java C++
代码文件间重复性测试
本文介绍了如何使用代码相似性检测工具simian来找出代码文件中的重复行,并通过示例指令展示了如何将检测结果输出到指定的文本文件中。
|
6天前
|
Python
Python 装饰器入门:让代码更灵活和可维护
Python 装饰器入门:让代码更灵活和可维护
11 1
|
7天前
|
Python
5-19|记录Python调用salt代码
5-19|记录Python调用salt代码
下一篇
无影云桌面