代码重复和性能之间的权衡

Trade off between code duplication and performance

Python 作为动态语言,提供了多种方式来实现相同的功能。这些选项在可读性、可维护性和性能方面可能有所不同。尽管我在 Python 中编写的常用脚本是一次性的,但我现在有一个我正在从事的(学术)项目必须可读、可维护并且性能相当好。由于我之前没有在 Python 中进行过任何认真的编码,包括任何类型的分析,因此我需要帮助来确定上面提到的三个因素之间的平衡。

这是我正在研究的科学包中的一个模块的代码片段。它是一棵 n 叉树 class,具有非常基本的骨架结构。这是在考虑继承和子 classing 的情况下编写的。

注意:在下面的代码中,树与节点相同。每棵树都是同一棵 class 树的实例。

class Tree(object):

    def __init__(self, parent=None, value=None):
        self.parent = parent
        self.value = value
        self.children = set()

下面的两个函数属于此 class(以及许多其他函数)

    def isexternal(self):
        """Return True if this is an external tree."""
        return not bool(self.children)

    def isleaf(self):
        """Return True if this is a leaf tree."""
        return not bool(self.children)

这两个函数做的是完全一样的事情——它们只是两个不同的名称。那么,为什么不将其更改为:

    def isleaf(self):
        """Return True of this is a leaf tree."""
        return self.isexternal()

我的疑惑是这些:

我读到 Python 中的函数调用相当昂贵(为每次调用创建新堆栈),但我不知道如果一个函数依赖于另一个函数是好事还是坏事.它将如何影响可维护性。这在我的代码中多次发生,我从另一个方法调用一个方法以避免代码重复。这样做是不好的做法吗?

这是同一 class 中此代码重复场景的另一个示例:

def isancestor(self, tree):
    """Return True if this tree is an ancestor of the specified tree."""
    return tree.parent is self or (not tree.isroot() 
        and self.isancestor(tree.parent))

def isdescendant(self, tree):
    """Return True if this tree is a descendant of the specified tree."""
    return self.parent is tree or (not self.isroot() 
        and self.parent.isdescendant(tree))

我可以选择:

def isdescendant(self, tree):
    """Return True if this tree is a descendant of the specified tree."""
    return tree.isancestor(self)

这种方法在不引入额外堆栈帧的情况下效果很好。

def isexternal(self):
    """Return True of this is an external tree."""
    return not bool(self.children)

isleaf = isexternal

在你的第二种情况下,这两种方法的算法根本不同。我觉得你提出的方案很好。

只是一个小测试:

>>> timeit('a()', setup="def a(): pass")
0.08267402648925781
>>> timeit('1+1')
0.03854799270629883

因此,与简单的算术表达式相比,一个简单的函数调用的时间不到 运行 的 2.5 倍。我不认为,它算作"rather expensive"。

非常 从广义上讲,优化有两种类型:宏观优化微观优化.宏优化包括您选择 算法 、决定不同的 数据结构 等。 对性能有很大影响的事情,如果您改变主意,通常会对您的代码库产生很大的连锁反应。从具有线性 O(n) 的数据结构切换到具有常量 O(1) 插入的数据结构可能是一个巨大的胜利,并且非常值得为此付出代价。添加缓存可能会将慢狗算法变成快如闪电的算法。

微优化是诸如消除或内联函数调用、消除或添加变量、缓存非常短的计算结果 window、展开循环等。通常,您应该忘记这些类型的优化和 关注代码的可读性和可维护性 。微优化的效果太小了,不值得。

您只应在分析您的代码后考虑这些类型的更改。如果您可以确定一个可以从这种优化中受益的关键循环,并且您的分析确认它会, 您进行更改并验证改进是否与另一轮分析一起工作--然后你应该微优化。

但在那之前,不要为小事担心。

def isdescendant(self, tree):
    """Return True if this tree is a descendant of the specified tree."""
    return tree.isancestor(self)

我绝对会推荐这种类型的代码重用。它使 crystal 明确 isdescendantisancestor 的倒数。它确保两个函数以相同的方式工作,因此您不会无意中在一个函数中引入错误,而在另一个函数中引入错误。

def isleaf(self):
    """Return True of this is a leaf tree."""
    return self.isexternal()

在这里我会问自己 isleafisexternal 在概念上是否相同。忽略它们的实现方式相同,它们在逻辑上是否相同?如果是这样,我会让一个人打电话给另一个人。如果只是碰巧他们有相同的实现,我可能会复制代码。您能想象这样一种场景,您想要更改一个功能而不是另一个吗?那将指向重复。

David Hess 的回答很好...

除此之外,not bool(x).

既不是最优的也不是规范的 Python

not x 给出完全相同的结果,并且是一次全局查找和一次函数调用更便宜。

此外,如果您在同一个调用中使用了两次 self.parent,您可能会考虑是否要将其放在本地 - parent = self.parent - 因为本地查找很多比实例查找便宜。当然,您应该 运行 计时以确保您获得收益。