为什么不用 Python?,第一部分

作者:Collin Park

编者按:本文自最初发表以来已更新。

自从我用 C 语言写了最初几百行代码以来,已经有 20 多年了,所以现在是时候学习一种更现代的语言了,也许是面向对象的语言。但是学什么呢?

运行自由软件的一大好处是安装介质中包含了许多编程语言。这就像美好的旧时光。那时,如果你买一台惠普 3000,你就能得到 Fortran、SPL/3000、链接器、调试器和印刷手册,更不用说自 1972 年以来对共享库的支持了。但有点跑题了。

今天,如果你购买专有系统并想用 ANSI C 语言对其进行编程,你可能不得不花费数百甚至数千美元购买某些供应商的编译器。除非你能找到免费软件,例如移植到你特定平台的 GNU 编译器集合 (GCC),情况才会不是这样。

现在,在美好的新时代,我的 SuSE 8.0 CD 上有 C、C++、Ruby、Perl、Python、Pascal 和 Fortran 77。我还有 Tcl、Scheme、Smalltalk、Modula-2、Forth、Prolog、REXX、LISP、Ada95 等等。

所以几年前,当我的十几岁的女儿对编程产生兴趣时,我们用 Python 完成了一些“玩具”程序。我听说对于初学者来说,Python 比 Perl 更好。Python 是最新的,它具有面向对象的特性,有相关的书籍,而且我看到了一些最近赞扬它的文章。所以我决定在一个稍微大一点的项目上试用一下。我将在本文的第二部分与您分享这段旅程,但现在我想我应该回顾一下我们对椰子问题的解答。

这个问题出自本·埃姆斯·威廉姆斯的一篇短篇小说,讲述了五个男人在一个荒岛上收集了一堆椰子。晚上,其中一个人醒来,决定提前拿走自己的那份。他将椰子分成五等份。剩下一个椰子,他把它扔给附近的一只猴子。他藏起自己的一份,并将剩下的四堆椰子合并,然后回去睡觉。

第二个人做了同样的事情,将现在稍微小一点的椰子堆分成五等份,将剩下的一个椰子扔给猴子,藏起自己的一份,然后回去睡觉。第三、第四和第五个人都重复了相同的模式。

当所有人在早上醒来时,椰子堆看起来小了,但每个人都和其他人一样有罪,所以没有人说什么。他们将剩下的椰子分成五等份,猴子在一旁观看。但是,这一次,没有剩下椰子。

他们最初有多少个椰子?

我记得答案小于 100,000,所以我们需要的是一个程序来做这件事

  • A. 使用 5 到大约 100,000 的 origPile 范围运行步骤 B-G

  • B. 设置 tempPile = origPile

  • C. 执行步骤 D-E 五次,即,为每个人执行一次

  • D. 如果 (tempPile mod 5) 不为 1,则尝试新的 origPile 值,即,不执行 E-G)

  • E. 将 tempPile 设置为 (tempPile-1) 的 4/5

  • F. 如果 (tempPile mod 5) 不为零,则尝试新的 origPile 值,即,不执行 G)

  • G. 打印 origPile 的值,并退出 A-G 循环

或者,用图示的方式来说,程序需要做这件事

Why Not Python?, Part 1
1980 年代语言的椰子程序

在过去,为了实现我上面概述的程序,我用 C 语言编写了以下代码

    1   main()
    2   {
    3      int origPile, tempPile, man;
    4      for (origPile=5; origPile<99999; ++origPile) {   //A
    5          tempPile = origPile;                         //B
    6          for (man=0; man<5; ++man) {                  //C
    7              if ((tempPile%5) != 1) goto foo;         //D
    8              tempPile = (tempPile-1) * 4 / 5;         //E
    9          }
   10          if (tempPile%5) continue;                    //F
   11          printf("Victory! %d\n", origPile);           //G
   12          break;
   13   foo:
   14      }
   15   }

这是 C 程序的简要描述;如果您愿意,可以跳过此部分。第 1-2 行只是 C 语言的样板代码,以便编译器知道您想运行什么作为您的主程序。

第 3 行是一个声明;也就是说,我们声明 origPile、tempPile 和 man 都是“int”类型的变量,“int”是整数的子集。

第 4 行本质上是摘要中的步骤 A。它告诉编译器,我们希望执行一些语句——直到第 14 行中匹配的 }——origPile 的范围从 5 到但不包括 99,999。如果语法对您来说不太明显,请不要担心;我们真正想讨论的是 Python,而不是 C,但这只是一个说明。

第 5 行执行摘要中的步骤 B。这很简单!

第 6 行执行步骤 C,它告诉编译器我们想执行一个循环——直到第 9 行中匹配的 }——五次,其中 “man” 取值为 0、1、2、3 和 4。

第 7 行执行步骤 D。它告诉编译器,如果 tempPile 除以 5 后没有正好剩下一个,我们想跳转到标签 foo:。在 Perl 中,你可以说

    next OuterLoop unless ($tempPile%5 == 1);

在 bash(1) 脚本中,你可以continue 2,但在 C 语言中,goto可能是实现此步骤的最简单方法。

第 10 行说,如果 tempPile 在早上不能均匀地分成五堆,则尝试新的 origPile 值。这本质上是步骤 F。第 11-12 行执行步骤 G。

所以基本上,程序应该可以工作。它真的可以吗?

    % make c0
    cc     c0.c   -o c0
    % ./c0
    Victory! 3121
    %

程序返回了一个好的答案吗?假设这五个人最初有 3,121 个椰子,正如程序所说。第一个人将 3,121 个椰子分成五堆,每堆 624 个,剩下一个。他把剩下的给猴子,藏起 624 个,然后回去睡觉。3,121-1-624 = 2,496。

第二个人将 2,496 个椰子分成五堆,每堆 499 个,剩下的一个给猴子。他藏起 499 个。2,496-1-499 = 1,996。

第三个人将 1,996 个椰子分成五堆,每堆 399 个,加上一个剩下的给猴子。他藏起 399 个。1,996-1-399 = 1,596。

第四个人将 1,596 个椰子分成五堆,每堆 319 个,剩下的一个给猴子,并藏起 319 个。1,596-1-319 = 1,276。

第五个人将 1,276 个椰子分成五堆,每堆 255 个,剩下一个给猴子。他藏起 255 个。1,276-1-255 = 1,020。

早上,还剩下 1,020 个椰子。这些椰子被分成五堆,每堆 204 个。所以程序给出了一个合理的答案。

Python 语言的椰子程序

我该如何用 Python 编写类似的程序呢?查看 Learning Python(Mark Lutz 和 David Ascher,O'Reilly 1999)中的一些示例,我注意到的第一件事是没有声明。也就是说,我们不说int origPile,我们只是开始使用一个名称。

就像在 Perl 或 shell 脚本中一样,第一次使用一个名称就会创建它的变量。我们也不必说main()。Python 认为您从第一个可执行语句开始就在 main 函数中。因此,基本的 Python 循环比基本的 C 迭代循环更容易编写。而不是写

    for (origPile=5; origPile<99999; ++origPile) {

你写

    for origPile in range(5,99999):

这可以是第 1 行。对于其余部分,使用这个怎么样

    1   for origPile in range(5,99999):             #A
    2      tempPile = origPile                      #B
    3      for man in range(5):                     #C
    4         if tempPile%5 != 1: break             #D
    5         tempPile = (tempPile-1) * 4 / 5;      #E
    6      else:                                    #if you didn't "break"
    7         if tempPile%5 != 0: continue          #F
    8         print "Victory!", origPile            #G
    9         break

以下是一些其他差异:您使用缩进而不是使用 { 花括号 } 来显示代码块的范围——循环或其他任何东西。您不需要在每个语句的末尾使用分号,尽管我确实在第 5 行不小心添加了一个无用的分号。Python 没有反对。注释用 # 而不是 // 或 /* */ 标记。

Python 没有 “goto” 语句,但在这种情况下我不需要它。循环之后的 “else” 是指如果您从不执行break.

明白了吗?好吧,我们把这段代码叫做 c0.py 并运行它

    % python c0.py
    Victory! 3121
    % 

这确实很简单!您不必成为天才也能立即编写出可用的 Python 代码。

这是我女儿写的代码(我添加了注释)

    1   import sys
    2   MoreCoconuts ="Whatever"
    3   
    4   for Coconuts in range(5,99999):                 #A
    5       Pile=Coconuts                               #B
    6       try:
    7           for dummy in range(0,5):                #C
    8               if (Pile%5)!=1: raise MoreCoconuts  #D
    9               Pile=(Pile-1)/5*4                   #E
   10           if Pile%5==0:                           #F (skip G if nonzero)
   11               print "Victory! "+`Coconuts`        #G
   12               sys.exit()
   13   
   14       except MoreCoconuts:
   15           continue

(我可能在这方面帮了她一点忙。)

因为我们想在第 12 行调用系统的退出例程,所以我们必须说import sys。当 Python 程序想要访问该系统调用时,它需要一个知道如何执行此操作的 Python 模块。事后看来,第 12 行也可以简单地编码为break,这也可以。那样的话,我们就不会需要在第 1 行中使用 “import sys” 了。但是,有时 sys.exit()(它会终止整个程序)正是您需要的。

try/except 结构对于 C++ 程序员来说可能很熟悉。“try” 块的基本思想是,在 “try” 块中的任何位置,您都可以 “raise” 一个异常,该异常在 “except” 块中被捕获。未捕获的异常会传递到下一个封闭的 “try” 块。如果在那里仍然未捕获,它们将传播到 Python 运行时系统,该系统通常会终止您的程序。

第 8 行说,如果当椰子堆被分成五个较小的堆时,没有正好剩下一个,我们需要放弃这个理论(或这个 “try”),并再次尝试更多的椰子。

第 10 行也可以这样编码

    if Pile%5 != 0: raise MoreCoconuts

就像在 Perl 中一样,在 Python 中有不止一种方法可以做到这一点。

因此,我们已经在一个玩具程序中看到了 C/Python 的一些差异。在第 2 部分中,我将向您展示一个解决 Sudoku 谜题的程序。

Collin Park 在 Network Appliance 工作,他在他的台式机和笔记本电脑上使用 Linux。他在家做数据恢复和其他与 Linux 相关的事情,他和他的妻子以及他们的两个十几岁的女儿住在一起。他们都使用 Linux 来满足他们的计算需求。

加载 Disqus 评论