Python 代码的效率与易读性?

Python 代码的效率与易读性?,python,Python,假设我有一个简单的函数,它计算一个数字的立方根并将其作为字符串返回: 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 后一个版本有额外的步骤,声明额外的变量,通过

假设我有一个简单的函数,它计算一个数字的立方根并将其作为字符串返回:

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的卑微任务,这两个函数对于外行来说都是不言自明的。但是,如果函数做了更复杂的事情,涉及数十或数百个代数运算或其他类型的操作,那么在什么时候应该简单地将所有这些写在函数的
返回
部分,或者在主体中详细说明所有(如果不是大多数)步骤,如上面的第二个示例


据我所知,函数的第一个版本看起来不那么清晰,但效率更高。如何以及何时平衡代码的易读性和效率,如本例中所示?

一般来说,您应该优先考虑代码的易读性而不是效率,但是如果您已经证明代码性能导致了问题,那么()您应该开始优化

如果您确实需要降低代码的易读性以加快速度,您可以始终使用注释来解释它正在执行的操作(甚至可能在注释中包含更可读的代码版本,以允许人们了解它正在执行的操作)


然而,要注意,通过注释而不是仅仅写易读的代码来解释代码的问题之一是注释可能会过时。如果您更改代码但不更新注释,那么您的注释将从有用的注释变成破坏每个人一天的黄鼠狼脸的说谎者-尽可能避免这种情况。

我想要一个易读的代码,但这实际上取决于您程序的性能有多重要,如果它不是一个性能要求很高的应用程序,我总是会让代码可读

如果函数做了更复杂的事情,涉及数十或数百次代数运算或其他类型的运算


对于更复杂的函数,将代码分解为几个小函数所获得的可读性带来了一个非常重要的好处:使代码易于测试。

我不相信你说的是代码的(运行时)效率,也就是说,它运行快还是内存/CPU消耗最少;相反,您谈论的是代码表达方式的效率/紧凑性

我也不认为你说的是代码的易读性,即外观和格式是否使代码易于解析;相反,你说的是可理解性,即阅读和理解可能涉及的复杂逻辑有多容易

紧凑性是一个客观的东西,你可以用线条或字符来衡量。可理解性是一个主观的东西,但大多数人认为什么是可理解的,什么是不可理解的,这是总的趋势。在你的例子中,第一个例子显然更紧凑,在我看来,它也更容易理解

您几乎总是希望将可理解性置于紧凑性之上,尽管在某些情况下(例如,这一次),两者可能会同时进行。然而,当这两个目标将代码拉向相反的方向时,可理解性应该始终获胜。可理解性有着明显的好处,它使代码在将来更容易修复、更改等,特别是它使其他人更容易修复、更改等。它使您更容易在以后回到它并验证它的正确性,以防有人怀疑它可能是某个bug的源

紧凑性给你带来的好处很少(除非你是在玩代码高尔夫)。我所看到的紧凑性的唯一一个小好处是帮助避免拥有过大的代码文件,因为如果您需要扫描大文件并快速了解代码的功能,这会使操作变得困难。但这只是一个很小的好处,而且通常有更好的方法来保持代码文件的合理大小(重构、重新组织)

  • 始终优先考虑可读性

  • 始终优先考虑可读性

  • 过早优化是有害的。所以一定要把可读性放在第一位

  • 一旦您的可读代码正常工作,如果性能是一个问题,或者它成为一个问题,在优化任何内容之前对代码进行分析。你不想浪费时间,降低可读性,优化一些不会给你带来太多好处的东西

  • 首先要优化那些在优化后仍然可读的东西;例如,将方法绑定到局部变量

  • 这些方法往往不会太多地提高性能(尽管将方法绑定到局部变量可能会在某些代码中产生很大的差异),但有时您可以看到一些更高效、更可读的东西,而这些东西您以前没有看到

  • 可读性的降低是否值得性能的提高是主观的,取决于每种情况下的重要性

  • 然后,如果您仍然需要优化,开始将要优化的代码部分分解为函数;这样,即使经过优化并降低可读性,主代码仍然是可读的,因为它只是调用一个函数
    do_something()
    ,而不必担心该函数中丑陋、优化的代码块

  • 我认为,即使使用可读代码,这也是一件好事,可以使其更具可读性
  • 如果每一点性能都有帮助,那么您可能希望将函数内联回到主代码中。这取决于您使用的Python实现,例如。
    return f(g(i(1, 2), j(3, 4)), h(k(5, 6), l(7, 8)))
    
    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]
    
    a = cuberoot4u(x)
    
    a = str(pow(x, 1./3.))