【问题标题】:Python Coding Style Dilemma [closed]Python编码风格困境[关闭]
【发布时间】:2015-09-26 03:49:08
【问题描述】:

有几个众所周知的 Python 代码样式规则,它们被认为是默认的,我会尽量遵守:

换行,使它们不超过 79 个字符。

保持缩进 4 个空格。

另一个常见的编程建议是

避免全局变量

换句话说,应该始终使用接受所有变量作为参数的函数,并避免直接从更高范围读取的类似 Pascal 的过程。

但是,在某些情况下,我们绝对应该打破其中的一些规则。例如,如果涉及具有长参数列表的函数。它们有两个不同的问题:

首先,在大量缩进的块中,剩下的空间太少了。

def function(variable1, variable2, variable3, variable4, variable5,\
variable6, variable7, variable8, variable9):
    return variable1 + variable2 + variable3 + variable4 + variable5 +\
    variable6 + variable7 + variable8 + variable9
def...
    for variable1...
        if variable 2...
            while variable3...
                if variable4...
                    for variable5...
                        ...
                                                    variable10 =\
                                                    function(\
                                                    variable1,\
                                                    variable2,\
                                                    variable3,\
                                                    variable4,\
                                                    variable5,\
                                                    variable6,\
                                                    variable7,\
                                                    variable8,\
                                                    variable9)
                                                    ...

这里的过程作为闭包,虽然被认为是一种不好的做法,但可能会有所帮助:

def...
    def procedure():
        variable10 = variable1 + variable2 + variable3 + variable4 +\
        variable5 + variable6 + variable7 + variable8 + variable9
    for variable1...
        if variable 2...
            while variable3...
                if variable4...
                    for variable5...
                        ...
                                                    procedure()
                                                    ...

另一个问题(实际上是特定于 python 的)是性能。如果函数参数很多,复制函数参数可能会变得非常昂贵:

import time

var1 = 1
var2 = 2
var3 = 3
var4 = 4
var5 = 5
var6 = 6

def function(var1, var2, var3, var4, var5, var6):
    pass

def procedure():
    pass

starttime = time.time()
for i in range(10000000):
    function(var1, var2, var3, var4, var5, var6)
finishtime = time.time()
print('Classical function runtime: {:.3f} s'.format(finishtime - starttime))

starttime = time.time()
for i in range(10000000):
    procedure()
finishtime = time.time()
print('Procedure runtime: {:.3f} s'.format(finishtime - start time))

这个输出:

Classical function runtime: 2.447 s
Procedure runtime: 1.180 s

因此,针对有经验的开发人员,我的问题是:

有什么可以证明在经典函数上使用类似 Pascal 的过程是合理的,或者应该不惜一切代价避免它们,即使它们会导致代码变得更庞大和更慢?

编辑:

使用*args**kwargs 只能部分解决问题,因为在函数调用期间仍需要列出所有参数。它也没有解决性能问题,因为参数仍在被复制。在 cmets 中提出的itertools 也并不总是适用。在某些情况下,解决嵌套问题非常棘手(考虑下面的代码)并且会花费大量开发时间,可能会导致代码非常纠结。

def my_function(**kwargs):
    with open(kwargs['file_a'], 'r') as input1:
        for line1 in input1:
            if kwargs['number'] % 3 == 0:
                if kwargs['number'] % 9 == 0:
                    with open(kwargs['file_0'], 'r') as input2:
                        for line2 in input2:
                            if line1.startswith('#'):
                                if line2.startswith('A'):
                                    with open('output.txt') as output1:
                                        for item in kwargs['items']:
                                            if item is in kwargs['good']:
                                                for sub in item:
                                                    if sub < 0:
                                                        result = process_vars(
                                                            kwargs['var1'],
                                                            kwargs['var2'],
                                                            kwargs['var3'],
                                                            kwargs['var4'],
                                                            kwargs['var5'],
                                                            kwargs['var6'],
                                                            kwargs['var7'],
                                                            kwargs['var8'],
                                                            kwargs['var9'],
                                                            kwargs['var10'])
                                                        output1.write(result)
                                                    elif sub >= 0 and sub < 1:
                                                        output1.write('hello')
                                                    else:
                                                        output1.write('byebye')
                                            elif len(item) > 20:
                                                item = item[: 20]
                                            else:
                                                output1.write(line2)
                                elif line2.startswith('B'):
                                    print('warning')
                                else:
                                    print('error')
                            elif line1.startswith('!'):
                                kwargs['wonders'].count += 1
                            else:
                                kwargs['threats'].append(line1)
                else:
                    kwargs['exceptions'].append(line1)
            elif kwargs['number'] % 3 == 1:
                with open(kwargs['file_1'], 'r') as input2:
                    ...
            elif kwargs['number'] % 3 == 2:
                with open(kwargs['file_2'], 'r') as input2:
                    ...

【问题讨论】:

  • 您应该真的考虑降低此处的嵌套级别。例如,使用 itertools.product() 删除所有嵌套的 for 循环。
  • (..)括号内你不需要反斜杠。
  • 经验法则:如果因为嵌套太深而需要打破行长规则:嵌套太深,请找出一种更有效的方法来避免嵌套。
  • 最后但同样重要的是,使用timeit() 模块来计时性能。而您的procedure()function() 的情况更可能是由于查找太多全局变量而不是在函数调用中使用参数造成的。
  • 一般来说,你的代码需要更广泛的重构。例如,不要使用(variable1, variable2, ..., variableN),而是使用(*variables),或者将相关参数分组到某种容器中。将此归咎于样式规则是一种红鲱鱼。

标签: python performance coding-style


【解决方案1】:
  1. 有人已经在 cmets 中提到了这一点:如果您有两个或更多嵌套的 for 循环,请尝试使用 itertools.product()

  2. 如果以上都做不到,至少可以减少if语句引起的缩进级别。示例:

    for abc in ..
        if (condition):
            for xyz in ...
    

    可以改为:

    for abc in ...
        if not (condition):
            continue
        for xyz in ...
    
  3. 如果要传递给方法的参数很多,请尝试使用*args(元组中的所有参数)或**kwargs(字典中的所有参数)。

编辑: 根据您的具体要求,我已经编辑了给定的代码。这可能不会产生与您完全相同的结果,但会让您大致了解如何实现上述 3 点。

def my_function(**kwargs):
    process_var_list = ['var1', 'var2', 'var3', 'var4', 'var5', 'var6',
                        'var7', 'var8', 'var9', 'var10']
    #Open all 3 files at one go. This will improve performance and reduce nested loops
    with open(kwargs['file_a'], 'r') as input1, open(kwargs['file_0'], 'r') as input2, open('output.txt') as output1:
        #following point 1
        for line1, line2, item in itertools.product(input1, input2, output1):
            if kwargs['number'] % 9 == 0:
                #following point 2
                if line1[0] not in ["#", "!"]:
                    kwargs['threats'].append(line1)
                    continue
                elif line1.startswith('!'):
                    kwargs['wonders'].count += 1
                    continue
                if line2[0] not in ['A', 'B']:
                    print('error')
                    continue
                elif line2.startswith('B'):
                    print('warning')
                    continue
                if item in kwargs['good']:
                    for sub in item:
                        if sub < 0:
                            var_args = {var: kwargs[var] for var in process_var_list}
                            #following point 3
                            result = process_vars(**var_args)
                            output1.write(result)
                        elif 1 < sub >= 0:
                            output1.write('hello')
                        else:
                            output1.write('byebye')
                elif len(item) > 20:
                    item = item[: 20]
                else:
                    output1.write(line2)
            elif kwargs['number'] % 3 == 0:
                kwargs['exceptions'].append(line1)
            elif kwargs['number'] % 3 == 1:
                with open(kwargs['file_1'], 'r') as input2:
                    pass
            elif kwargs['number'] % 3 == 2:
                with open(kwargs['file_2'], 'r') as input2:
                    pass

【讨论】:

  • 考虑到我的编辑,您能否扩展您的答案?
  • 代码产生完全不同的结果。关键是我不能申请itertools.product(),因为我事先不知道,我将在循环#2中迭代哪个对象或文件(如果有),直到我检查所有条件,这又取决于在循环 #1 中生成的项目。我也不能使用var: kwargs[var],因为kwargs 中有变量,不能传递给函数。因此,您只是通过简化任务本身来减少嵌套。
  • @Roman 是的,这就是为什么我提出免责声明它可能不会产生相同的结果。这是对如何应用上述 3 点的总体思路。也许您可以尝试重构代码并使用单独的函数,而不是用一种方法编写整个代码。
  • 这里的重写真的很棘手而且很耗时,因为逻辑非常分支,并且在更改之后必须重新调试所有内容。这就是为什么我正在考虑改变标准编码风格(减少缩进和更长的行)或制作更多独立的功能。在后来的情况下,我正在考虑直接使用更高范围的变量的“程序”,因为这使脚本更短、更快并且消耗更少的内存。但是,我对此表示怀疑,因为许多程序员称这是一种不好的做法。这就是我的问题。
猜你喜欢
  • 1970-01-01
  • 2019-10-12
  • 2015-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-20
相关资源
最近更新 更多