《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

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

相关文章
|
23天前
|
机器学习/深度学习 算法 安全
【PSO-LSTM】基于PSO优化LSTM网络的电力负荷预测(Python代码实现)
【PSO-LSTM】基于PSO优化LSTM网络的电力负荷预测(Python代码实现)
|
25天前
|
调度 Python
微电网两阶段鲁棒优化经济调度方法(Python代码实现)
微电网两阶段鲁棒优化经济调度方法(Python代码实现)
|
25天前
|
供应链 新能源 调度
微电网调度(风、光、储能、电网交互)(Matlab&Python代码实现)
微电网调度(风、光、储能、电网交互)(Matlab&Python代码实现)
|
26天前
|
安全 数据处理 Python
Python 函数式编程:让代码更简洁高效
Python 函数式编程:让代码更简洁高效
351 107
|
21天前
|
程序员 测试技术 开发者
Python装饰器:简化代码的强大工具
Python装饰器:简化代码的强大工具
146 92
|
24天前
|
机器学习/深度学习 数据采集 算法
【CNN-BiLSTM-attention】基于高斯混合模型聚类的风电场短期功率预测方法(Python&matlab代码实现)
【CNN-BiLSTM-attention】基于高斯混合模型聚类的风电场短期功率预测方法(Python&matlab代码实现)
|
22天前
|
运维 算法 新能源
基于风光储能和需求响应的微电网日前经济调度(Python代码实现)
基于风光储能和需求响应的微电网日前经济调度(Python代码实现)
|
23天前
|
机器学习/深度学习 算法 调度
【切负荷】计及切负荷和直流潮流(DC-OPF)风-火-储经济调度模型研究【IEEE24节点】(Python代码实现)
【切负荷】计及切负荷和直流潮流(DC-OPF)风-火-储经济调度模型研究【IEEE24节点】(Python代码实现)
|
24天前
|
机器学习/深度学习 算法 调度
【EI复现】基于深度强化学习的微能源网能量管理与优化策略研究(Python代码实现)
【EI复现】基于深度强化学习的微能源网能量管理与优化策略研究(Python代码实现)

推荐镜像

更多