《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

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

相关文章
|
11月前
|
存储 算法 调度
【复现】【遗传算法】考虑储能和可再生能源消纳责任制的售电公司购售电策略(Python代码实现)
【复现】【遗传算法】考虑储能和可再生能源消纳责任制的售电公司购售电策略(Python代码实现)
490 26
|
11月前
|
测试技术 开发者 Python
Python单元测试入门:3个核心断言方法,帮你快速定位代码bug
本文介绍Python单元测试基础,详解`unittest`框架中的三大核心断言方法:`assertEqual`验证值相等,`assertTrue`和`assertFalse`判断条件真假。通过实例演示其用法,帮助开发者自动化检测代码逻辑,提升测试效率与可靠性。
655 1
|
10月前
|
测试技术 Python
Python装饰器:为你的代码施展“魔法”
Python装饰器:为你的代码施展“魔法”
410 100
|
10月前
|
开发者 Python
Python列表推导式:一行代码的艺术与力量
Python列表推导式:一行代码的艺术与力量
628 95
|
11月前
|
Python
Python的简洁之道:5个让代码更优雅的技巧
Python的简洁之道:5个让代码更优雅的技巧
438 104
|
11月前
|
开发者 Python
Python神技:用列表推导式让你的代码更优雅
Python神技:用列表推导式让你的代码更优雅
729 99
|
11月前
|
IDE 开发工具 开发者
Python类型注解:提升代码可读性与健壮性
Python类型注解:提升代码可读性与健壮性
506 102
|
10月前
|
缓存 Python
Python装饰器:为你的代码施展“魔法
Python装饰器:为你的代码施展“魔法
560 88
|
10月前
|
监控 机器人 编译器
如何将python代码打包成exe文件---PyInstaller打包之神
PyInstaller可将Python程序打包为独立可执行文件,无需用户安装Python环境。它自动分析代码依赖,整合解释器、库及资源,支持一键生成exe,方便分发。使用pip安装后,通过简单命令即可完成打包,适合各类项目部署。
1719 68
|
10月前
|
安全 Java 测试技术
《深入理解Spring》单元测试——高质量代码的守护神
Spring测试框架提供全面的单元与集成测试支持,通过`@SpringBootTest`、`@WebMvcTest`等注解实现分层测试,结合Mockito、Testcontainers和Jacoco,保障代码质量,提升开发效率与系统稳定性。

推荐镜像

更多