我开始使用Java和C ++进行编程,因此我习惯于使用一个“ main”函数来调用执行实际工作的其他函数。在大学里,我总是被告知,在主函数中进行实际计算是不好的做法。我目前正在使用Python,并且在弄清楚如何编写一个不错的“ main”函数时遇到麻烦,尤其是因为我正在做不需要单独类的小事情。

您如何看待以下代码?主要功能是必需的,还是只编写没有功能的所有内容?在Python世界中是否对此有一个普遍的同意?

# Finds sum of all multiples of 3 and 5 from 0 to 999 

def find_multiples():
    global numbers

    for i in range(0,1000):
       if i%3==0 or i%5==0:
           numbers.append(i);

numbers = []

if __name__ == '__main__':
    find_multiples()
    print sum(numbers)


#1 楼

在我见过的大多数Python代码中,通常会定义一个主要函数,您可以根据建立脚本的条件调用该函数,但该函数未导入。我相信这是在Python中执行操作的标准方式,但是我也不相信它实际上是任何地方的书面规则,尽管这也是他们在文档中的做法。

评论


\ $ \ begingroup \ $
让我们清楚地说明这是什么成语,因为上面的所有注释的格式都不正确,并且该成语不可见:if __name__ ==“ __main__”:(如在Mark Loeser所链接的页面上所述)。
\ $ \ endgroup \ $
–塔德克
2012年1月24日20:20

#2 楼

这是一些肤浅的评论。除了测试网站和注释格式外,还有其他更多功能,但是:创建主要功能(对我们的基准测试人员有很大帮助),并认为可以导入您的模块,因此文档字符串和局部变量会有所帮助。

# Finds...
###-^ Put this in a docstring

def find_multiples():
    """Finds sum of all multiples of 3 and 5 from 0 to 999 """
###-^ This allows you to "from x import find_multiples,  help(find_multiples)"
    numbers = []
###-^ Avoid globals
    for i in xrange(1000):
###-^ Use xrange if Python 2.x, no need to start at 0 (it's the default)
       if not (i % 3) or not (i % 5):
###-^ Add spaces around operators, simplify
###-^ the boolean/numeric checks
           numbers.append(i)
###-^ Remove trailing ;
    return numbers

###-^ Removed global

def main():
###-^ Allows calling main many times, e.g. for benchmarking
    numbers = find_multiples()
    print sum(numbers)

if __name__ == '__main__':
    main()


评论


\ $ \ begingroup \ $
xrange(1000)就足够了。哪个版本更具可读性是有争议的。否则,优秀的代码审查! Chapeau bas!
\ $ \ endgroup \ $
–博洛
2011年1月20日在21:57



\ $ \ begingroup \ $
我很想(尽管可能不是这样的小程序)将print语句移出main,并严格保留main()函数以捕获异常,将错误消息打印到stderr并返回error /成功代码到外壳。
\ $ \ endgroup \ $
–威廉·佩恩(William Payne)
2012年6月8日17:51

\ $ \ begingroup \ $
使用“主函数”的另一个原因是,否则,您声明的所有变量都是全局变量。
\ $ \ endgroup \ $
–托马斯·阿勒
2014-09-28 14:53

#3 楼

UNIX Man建议使用生成器而不是列表是一个很好的建议。但是,我建议在yield上使用生成器表达式:

def find_multiples(min=0, max=1000):
    """Finds multiples of 3 or 5 between min and max."""
    return (i for i in xrange(min, max) if i%3==0 or i%5==0)


这与使用yield具有相同的好处,并且更加简洁。与UNIX Man的解决方案相比,它还使用“正”控制流,即它选择要选择的元素,而不是要跳过的元素,并且缺少continue语句简化了控制流¹。

更一般的说,我建议重命名函数find_multiples_of_3_and_5,因为否则名称会建议您使用它查找任何数字的倍数。甚至更好:您可以泛化函数,以便它可以找到任何数字的倍数。为此,代码看起来可能像这样:

def find_multiples(factors=[3,5], min=0, max=1000):
    """Finds all numbers between min and max which are multiples of any number
       in factors"""
    return (i for i in xrange(min, max) if any(i%x==0 for x in factors))


但是现在生成器表达式变得有点拥挤,因此我们应该考虑用于查找给定数字是否为逻辑的逻辑

def find_multiples(factors=[3,5], min=0, max=1000):
    """Finds all numbers between min and max which are multiples of any number
       in factors"""
    def is_multiple(i):
        return any(i%x==0 for x in factors)

    return (i for i in xrange(min, max) if is_multiple(i))

当然,使用yield的解也可以肯定地编写,而无需continue

评论


\ $ \ begingroup \ $
我喜欢你的第二个版本。也许您可以通过删除min,max参数并使其带有迭代器参数来使其更加通用和简洁。这样,剩下的程序将是sum(find_multiples(xrange(1000)))
\ $ \ endgroup \ $
–托马斯·阿勒
2014-09-28 14:55



#4 楼

这是我的处理方法:

def find_multiples(min=0, max=1000):
    """Finds multiples of 3 or 5 between min and max."""

    for i in xrange(min, max):
       if i%3 and i%5:
           continue

       yield i

if __name__ == '__main__':
    print sum(find_multiples())


这使find_multiples成为生成的倍数的生成器。倍数不再需要显式存储,尤其是不需要全局存储。

现在还可以使用参数(具有默认值),以便调用者可以指定要搜索的数字范围。

当然,全局的“ if”块现在只需要对函数生成的数字求和,而不必希望全局变量存在并且不会被其他任何可能的事物所影响。 br />

#5 楼

通常,python中的所有内容都是按照我们共同成长的原则处理的。这使您可以选择所需的操作方式。但是,最佳做法是将主函数代码放入函数中,而不是直接放入模块中。 >
但是避免使用全局变量(对numbers所做的操作)作为返回值,因为这会使第二次执行该函数变得困难。

评论


\ $ \ begingroup \ $
“ ...因为这样使第二次执行该函数变得困难。”您的意思是说,在主函数中第二次调用find_multiples之前,我需要清空数字?
\ $ \ endgroup \ $
–罗勒
2011年1月21日在18:41

\ $ \ begingroup \ $
@罗勒。
\ $ \ endgroup \ $
–格雷戈·穆勒格(GregorMüllegger)
2011-2-8在9:37

#6 楼

关于main()函数的使用。

使用类似这样的结构的一个重要原因:反过来更可重用。导入模块时,我无法真正重用运行各种代码的模块。如上所述,通过具有main()函数,我可以导入模块并重用模块的相关部分。甚至可以在我方便的时候运行main()函数。

#7 楼

以防万一,您不熟悉上面使用的生成器技术,以下是通过三种方式完成的相同功能:从接近原始功能的地方开始,然后使用列表推导,然后使用生成器表达式:

# Finds sum of all multiples of 3 and 5 from 0 to 999 in various ways

def find_multiples():
    numbers = []
    for i in range(0,1000):
        if  i%3 == 0 or i%5 == 0: numbers.append(i)
    return numbers

def find_multiples_with_list_comprehension():
    return [i for i in range(0,1000) if  i%3 == 0 or i%5 == 0]

def find_multiples_with_generator():
    return (i for i in range(0,1000) if  i%3 == 0 or i%5 == 0)

if __name__ == '__main__':
    numbers1 = find_multiples()
    numbers2 = find_multiples_with_list_comprehension()
    numbers3 = list(find_multiples_with_generator())
    print numbers1 == numbers2 == numbers3
    print sum(numbers1)


find_multiples()与您的工作非常接近,但是Pythonic稍多一些。它避免了global(icky!)并返回列表。

生成器表达式(包含在圆括号中,如元组)比列表理解(包含在方括号中,如列表)更有效。实际上不返回值列表-它们返回可以迭代的对象。

所以这就是为什么我在list()上调用find_multiples_with_generator()的原因,这实际上是毫无意义的,因为您也可以简单地执行sum(find_multiples_with_generator(),这是您最终的目标。我只是想向您展示生成器表达式和列表推导看起来相似但行为不同。 (这让我很早就被绊倒了。)

#8 楼

我会保持简单。在这种情况下,我会这样做:

def my_sum(start, end, *divisors):
    return sum(i for i in xrange(start, end + 1) if any(i % d == 0 for d in divisors))

if __name__ == '__main__':
    print(my_sum(0, 999, 3, 5))


因为它足够可读。如果需要实现更多功能,则添加更多功能。

当然也有O(1)版本(如果假定除数的数量恒定)。

注意:在Python 3中没有xrnage,因为range很懒。

评论


\ $ \ begingroup \ $
很清楚。也许不要在范围内包含结束参数。如果我们总是总是像范围内那样使范围互斥,则会更容易。如果这样做的话,我们就不必考虑要再次传递什么值了:)
\ $ \ endgroup \ $
–托马斯·阿勒
2014年9月28日在15:00

#9 楼

为@pat答案添加更多内容,类似于下面的功能没有任何意义,因为您不能将其重复用于类似的任务。 (已复制的剥离注释和文档字符串。)

def find_multiples():
    numbers = []
    for i in xrange(1000):
       if not (i % 3) or not (i % 5):
           numbers.append(i)
    return numbers


#10 楼

这是使用ifilter的一种解决方案。基本上,它的作用与使用生成器相同,但是由于您尝试滤除不满足某个函数的数字,如果该函数可被所有因素整除,则该函数返回true,也许它可以更好地捕获您的逻辑。对于不习惯功能逻辑的人来说,可能有点难以理解。

all(iterable)函数返回true,如果iterable中作为参数传递的所有元素都是true

from itertools import ifilter

def is_multiple_builder(*factors):
    """returns function that check if the number passed in argument is divisible by all factors"""
    def is_multiple(x):
        return all(x % factor == 0 for factor in factors)
    return is_multiple

def find_multiples(factors, iterable):
    return ifilter(is_multiple_builder(*factors), iterable)


将返回一个因子为true / false的生成器,具体取决于该因子是否可被数字整除。我可以省略此表达式周围的括号,因为它是all的唯一参数。