假设我有一个简单的函数来计算数字的立方根,并将其作为字符串返回:
def cuberoot4u(number):
return str(pow(number, 1/3))
我可以将其重写为:
def cuberoot4u(number):
cube_root = pow(number, 1/3)
string_cube_root = str(cube_root)
return string_cube_root
后一版本有额外的步骤来声明额外的变量,这些变量通过每个相应的操作显示值;乘以1/3并转换为字符串 - 代码看起来更容易理解。
现在,对于找到Cuberoot这样一个琐碎的任务,这两个功能对于外行人来说似乎是不言自明的。但是,如果函数做了一些更复杂的事情,涉及数十或数百个代数操作或其他类型的操作,那么在什么时候应该只在函数的return
部分写下所有这些,或者代之以详细说明,如果不是大多数,像上面第二个例子一样在主体中进行操作?
根据我的理解,该功能的第一个版本似乎不太清晰但效率更高。如何以及何时在此示例中的代码中平衡易读性与效率?
答案 0 :(得分:9)
一般情况下,您应该优先考虑代码中的易读性而不是效率,但是如果您已经证明代码性能导致问题,那么(and only then)应该开始优化。
如果您确实需要使代码不那么清晰,以便加快速度,您可以随时使用注释来解释它正在做什么(甚至可能包括评论中更易读的代码版本,以便人们关注它在做什么)。
请注意,通过注释解释代码而不是仅编写清晰代码的问题之一是注释可能会过时。如果您更改了代码但没有更新评论,那么您的评论就会从一个有用的评论变成一个狡猾的骗子,它会毁掉每个人的一天 - 如果可能的话尽量避免这样做。
答案 1 :(得分:3)
始终优先考虑可读性。
始终优先考虑可读性。
过早优化是邪恶的。因此,请始终优先考虑可读性。
一旦您的可读代码正常运行,如果性能出现问题,或如果成为问题, 在优化任何内容之前,对您的代码进行分析 。你不想浪费时间和降低可读性,优化那些不会给你带来很多好处的东西。
首先优化在优化之后仍然可读的内容;例如,将方法绑定到局部变量。
这些往往不会过多地提高性能(虽然对局部变量的绑定方法可以在某些代码中产生很大的差异),但有时你可以看到更高效的东西,就像你之前错过的那样。 / p>
X的可读性降低是否值得Y性能增加是主观的,取决于每种情况在特定情况下的重要性。
然后,如果仍然需要优化,请开始将您要优化的代码部分分解为函数;这样,即使在它们被优化并且不太可读之后,主代码仍然可读,因为它只是调用函数do_something()
而不用担心代码中丑陋,优化的代码块功能
如果每一点点性能都有帮助,那么你可能想要将函数内联回主代码。这取决于您使用的Python的实现,例如CPython与PyPy。
如果一大堆优化到地狱的Python代码不够快......用C语言重写它。
答案 2 :(得分:3)
我讨厌那些认为通过添加更多方法使代码更具可读性的开发人员。
a = cuberoot4u(x)
代码要求我查找函数以了解正在发生的事情。
a = str(pow(x, 1./3.))
没有不必要的功能cuberoot4u
非常可读。
不要按大小或详细程度来衡量可读性。你需要的是代码
这样可以轻松验证,轻松调试,而且误用。上面的功能假装它做了一些复杂的事情,但事实并非如此 - 这很糟糕。它隐藏了类型转换,这也很糟糕。 “内联”版本非常清楚一个数学运算,然后转换为字符串。
答案 3 :(得分:2)
我不相信你在谈论(运行时)代码的效率,即它是否运行速度快或内存/ CPU消耗最少;相反,你正在谈论代码表达方式的效率/紧凑性。
我也不认为你在谈论代码的易读性,即外观和格式化使代码易于解析;相反,你在谈论可理解性,即阅读它并理解所涉及的潜在复杂逻辑是多么容易。
紧凑是一个客观的事情,你可以用线条或字符来衡量。可理解性是一种主观的东西,但大多数人都能找到可理解的概念,而不是。在你的情况下,第一个例子显然更紧凑,在我看来,它的也更容易理解。
你几乎总是希望优先考虑对紧凑性的可理解性,尽管在某些情况下(例如这一个),这两者可能是齐头并进的。然而,当两个目标在相反的方向上拉你的代码时,可理解性总是应该赢。可理解性具有明显的好处,它使代码在将来更容易修复,更改等,特别是它使其他人更容易修复,更改等。这使您以后更容易回到它并验证其正确性,如果出现一些疑问,可能是某些错误的来源。
紧凑性很少给你买(除非你打高尔夫球码)。我能看到的紧凑性的唯一一个小好处是帮助避免使用过大的代码文件,因为如果你需要扫描大文件并快速了解代码的作用,就会很难。但这是一个非常小的好处,并且通常有更好的方法将代码文件保持在合理的大小(重构,重组)。
答案 4 :(得分:0)
我希望有一个清晰易读的代码,但它实际上取决于程序性能的重要程度,如果它不是一个具有严格性能要求的应用程序,那么我总是将代码视为可读。
如果函数做了更复杂的事情,涉及数十或数百个代数操作或其他类型的操作
使用更复杂的功能,通过在几个小函数中破解代码实现的可读性带来了非常重要的好处:使代码易于测试。
答案 5 :(得分:0)
绝对写出易读性。将对象绑定到名称的开销非常小,即使对象很大,例如大型列表或字典,它也不会产生任何影响,因为被复制的唯一内容是对对象的引用,进行简单的赋值时,不会复制对象本身的字节数。
但是,您的第一个示例相当容易阅读,因为嵌套是线性的。如果它是非线性的,比如
return f(g(i(1, 2), j(3, 4)), h(k(5, 6), l(7, 8)))
我绝对建议分手。但是对于简单的线性嵌套表达式,一个很好的经验法则是:如果它太长而无法放在一条PEP-008线上,你应该将其分解。
这里有一些代码来说明两个函数的速度几乎没有差别。我还添加了一个 更高效的版本:它使用**
运算符来执行取幂,这样可以节省函数调用的开销。
此代码首先打印每个函数的Python字节码。然后,它使用一小组数据运行函数,以验证它们是否实际执行了它们应该执行的操作。最后它执行时序测试。请注意,有时您的三行版本有时比单行版本更快,具体取决于系统负载。
from __future__ import print_function, division
from timeit import Timer
import dis
def cuberoot_1line(number):
return str(pow(number, 1/3))
def cuberoot_1lineA(number):
return str(number ** (1/3))
def cuberoot_3line(number):
cube_root = pow(number, 1/3)
string_cube_root = str(cube_root)
return string_cube_root
#All the functions
funcs = (
cuberoot_1line,
cuberoot_3line,
cuberoot_1lineA,
)
def show_dis():
''' Show the disassembly for each function '''
print('Disassembly')
for func in funcs:
fname = func.func_name
print('\n%s' % fname)
dis.dis(func)
print()
#Some numbers to test the functions with
nums = (1, 2, 8, 27, 64)
def verify():
''' Verify that the functions actually perform as intended '''
print('Verifying...')
for func in funcs:
fname = func.func_name
print('\n%s' % fname)
for n in nums:
print(n, func(n))
print()
def time_test(loops, reps):
''' Print timing stats for all the functions '''
print('Timing tests\nLoops = %d, Repetitions = %d' % (loops, reps))
for func in funcs:
fname = func.func_name
print('\n%s' % fname)
setup = 'from __main__ import nums, %s' % fname
t = Timer('[%s(n) for n in nums]' % fname, setup)
r = t.repeat(reps, loops)
r.sort()
print(r)
show_dis()
verify()
time_test(loops=10000, reps=5)
典型输出
Disassembly
cuberoot_1line
27 0 LOAD_GLOBAL 0 (str)
3 LOAD_GLOBAL 1 (pow)
6 LOAD_FAST 0 (number)
9 LOAD_CONST 3 (0.33333333333333331)
12 CALL_FUNCTION 2
15 CALL_FUNCTION 1
18 RETURN_VALUE
cuberoot_3line
33 0 LOAD_GLOBAL 0 (pow)
3 LOAD_FAST 0 (number)
6 LOAD_CONST 3 (0.33333333333333331)
9 CALL_FUNCTION 2
12 STORE_FAST 1 (cube_root)
34 15 LOAD_GLOBAL 1 (str)
18 LOAD_FAST 1 (cube_root)
21 CALL_FUNCTION 1
24 STORE_FAST 2 (string_cube_root)
35 27 LOAD_FAST 2 (string_cube_root)
30 RETURN_VALUE
cuberoot_1lineA
30 0 LOAD_GLOBAL 0 (str)
3 LOAD_FAST 0 (number)
6 LOAD_CONST 3 (0.33333333333333331)
9 BINARY_POWER
10 CALL_FUNCTION 1
13 RETURN_VALUE
Verifying...
cuberoot_1line
1 1.0
2 1.25992104989
8 2.0
27 3.0
64 4.0
cuberoot_3line
1 1.0
2 1.25992104989
8 2.0
27 3.0
64 4.0
cuberoot_1lineA
1 1.0
2 1.25992104989
8 2.0
27 3.0
64 4.0
Timing tests
Loops = 10000, Repetitions = 5
cuberoot_1line
[0.29448986053466797, 0.29581117630004883, 0.29786992073059082, 0.30267000198364258, 0.36836600303649902]
cuberoot_3line
[0.29777216911315918, 0.29979610443115234, 0.30110907554626465, 0.30503296852111816, 0.3104550838470459]
cuberoot_1lineA
[0.2623140811920166, 0.26727819442749023, 0.26873588562011719, 0.26911497116088867, 0.2725379467010498]
在Linux上运行Python 2.6.6的2GHz机器上测试。