原文: http://www.hetland.org/python/instant-hacking.php
Instant Hacking[译文]
译者: 肯定来过
1. 运行环境
要用python写程序,你必须先安装一个python的解释器。它可以存在于大多数平台(包括Macintosh、Unix和Windows)。更多与此有关的信息可以在python的网站上找到。你还应该有一个文本编辑器(象emacs、notepad或者类似的东西)。
- 编程是什么?
为计算机写程序其实就是给它一系列的指令告诉它去做什么。计算机程序在某些方面就象是菜谱,指导我们如何做菜的那种。例如:
假日火腿沙拉
原料:
腌泡汁:
1/4杯酸橙汁
1/4杯低钠大豆酱油
1/4杯水
1大汤匙植物油
3/4茶匙小茴香
1/2茶匙牛至
1/4茶匙热胡椒粉
2片丁香、大蒜,捣碎
沙拉:
1份(12盎司)罐装少钠午餐肉火腿切成条状
1个洋葱,切片
胡椒粉,切好的生菜
12个樱桃西红柿,切半
方法:
把腌泡汁装在有合适盖子的广口瓶里摇匀。用塑料袋装上火腿,泼上腌泡汁,封住袋口。在电冰箱里腌制30分钟。从塑料袋里取出火腿;准备2大汤匙腌泡汁,在煮锅里煮一下。加上火腿、洋葱、绿色的胡椒。烧3到4分钟直到火腿熟了为止……
当然,没有一台计算机会懂这个……而且即便是懂,大多数计算机也不可能烧制出一份沙拉。那么,我们该如何让这些变得对计算机来说更为友好一些呢?从根本上说依赖于两点:首先,我们必须以计算机可以理解的方式与之交流;其次还要和它谈论它能够做到的事情。
第一点意味着我们必须使用一种语言——一种已经为之准备好了解释器的程序设计语言,第二点意味着我们不能期望计算机为我们做一份沙拉——但是我们可以让它做数字累加或者在屏幕上打印东西之类的事情。
- Hello……
程序设计教程有一个传统,通常以在屏幕上打印“Hello, world!”这样的程序做为开始。对python来说,这非常简单:
1 print "Hello, world!"
它从根本上说很象上面的菜谱(尽管要短得多!)。它告诉计算机做什么:打印“Hello, world!”。如果让它打印更多的废话该怎么做呢?很简单:
1 print "Hello, world!"
2 print "Goodbye, world!"
不比上一个难,是不是?但是不怎么有趣……我们希望它可以处理更多的元素,就象沙拉菜谱那样。那么,我们都有哪些元素呢?首先,有字符串,象“Hello, world!”,除此之外还有数字。假设我们打算让计算机为我们计算矩形的面积。我们可以给它如下的菜谱:
1 # The Area of a Rectangle
2 # Ingredients
3 width=20
4 height=30
5 #Instructions:
6 area = width*height
7 print area
你大概可以看出它同火腿沙拉菜谱的相似性(尽管有些细微的差别)。但它是如何工作的呢?首先,以#开始的行叫做注释事实上会被计算机忽略。然而插入象这样小段的注释对于增强你程序的可读性来说是很重要的。
接下来,看起来象 foo = bar 这样的行叫做赋值。对于 width = 20 这样的情况来说就是告诉计算机从这里开始width就代表20了。它还意味着一个名字为“width”的变量从此被创建了(如果它先前已经存在,那么会被重新覆盖)。所以,我们以后使用这个变量的时候,计算机就知道了它的值。因此,
width * height本质上同 20 * 30 一样会计算出600这个结果,然后赋给名称为“area”的变量。程序的最后一句在屏幕上打印出变量“area”的值,所以你看到这个程序运行的最终结果仅仅是 600
注意:在某些程序设计语言中,你必须在程序开始的时候告诉计算机你将会用到哪些变量(就象沙拉中的元素)——而python足够聪明,所以你可以根据需要随时创建。
- 反馈
现在,你可以执行一些简单,或者再复杂一点的计算了。比方说,你或许打算写一段程序来计算圆形的面积而不是矩形的:
1 radius = 30
2 print 3.14*radius*radius
然而,这事实上并不比计算矩形面积的那个程序更有意思。至少在我看来是这样。它有些僵硬。如果我们看到半径为31的圆该怎么办?怎样让计算机知道?这有点象沙拉菜谱中的:“烧3到4分钟直到火腿熟了为止。”要知道何时烧熟,我们必须检查。我们需要反馈,或者提示。计算机如何知道我们圆形的半径?同样需要输入资料……我们可以做的是告诉计算机半径是多少:
1 radius = input("What is the radius?")
2 print radius * radius * 3.14
现在程序变得漂亮一些了……input是个被称为函数的东西。(很快你将学习创建你自己的函数。而input是python内建的函数。)仅仅写下 input ,什么也不会做……你必须在它的后面放上一对括号。所以input()可以工作
——它会简单的要求用户输入半径的长度。而上面的那个版本对用户来说也许更友好一些,因为它首先打印出了一个问题。当我们将诸如提问字符串“What is the radius?”之类的东西放在函数调用的括号中时,这个过程被称为函数的参数传递。括号中的内容被称为参数。在上个例子中我们传递了一个提问作为参数以便input知道在获得答案前应该先打印什么。
但是获得的答案如何到达radius变量呢?函数input,调用时,会返回一个值(象许多其它函数一样)。你不一定非要使用这个值,但象我们这种情况,我们要使用它。这样,下面这两个表达式有着很大的差别:
1 foo = input #foo现在包含input函数本身
2 bar = input() #bar包含用户键入的值
foo现在包含input函数本身(所以它事实上可以象foo(“What is your age?”)这样使用;这被称为动态函数调用),而bar包含用户键入的值。
- 流程
现在我们可以编写程序执行简单的任务(运算和打印)并且可以获得用户输入了。这很有用,但仍然局限在按顺序执行命令,也就是说——它们必须按照事先安排好的顺序执行。大多数火腿沙拉菜谱是象这样顺序或者线性叙述的。但是如果我们打算让计算机检查沙拉是否烧好该怎样告诉它呢?如果烧好了,那么应该从烘箱里把它取出来——否则的话,应该接着让它烧更长一段时间什么的。我们如何表达这个?
我们想做的,其实是控制程序的流程。它可以从两个方向执行——要么拿开火腿,要不继续让它留在烘箱里。我们可以选择,条件是它是否烧好。这被称为条件执行。我们可以这样写:
1 temperature = input("What is the temperature of the spam?")
2 if temperature > 50:
3 print "The salad is properly cooked."
4 else:
5 print "Cook the salad some more."
意思很明显:如果温度超过50(摄氏度),那么打印出信息告诉用户烧好了,否则,告诉用户再烧制一段时间。
注意:缩进在python中很重要。条件执行(还有循环执行以及函数定义——见后面)中的语句块必须被缩进(而且要缩进同等数量的空格;一个键相当于8个空格)以便解释器可以知道它们从哪里开始到哪里结束。这同时也使程序变得
1 # Area calculation program
2 print "Welcome to the Area calculation program"
3 print "---------------------------------------"
4 print
5 # Print out the menu:
6 print "Please select a shape:"
7 print "1 Rectangle"
8 print "2 Circle"
9 #Get the user's choice:
10 shape = input(">; ")
11 #Calculate the area:
12 if shape == 1:
13 height = input("Please enter the height: ")
14 width = input("Please enter the width: ")
15 area = height *width
16 print "The area is ", area
17 else:
18 radius = input("Please enter the radius: ")
19 area = 3.14 * (radius**2)
20 print "The area is ",
rea中的新东西: 只使用print本身将打印出一个空行
2. ==检查两个值是否相等,与=不同,后者把表达式右侧的值赋给左侧的变量。这是一个非常重要的差别!
3. **是python的幂运算符——因此半径的平方被写成radius**2
4. print能够打印出不止一个东西。只要用逗号把它们分开就可以了。(它们在输出时会用单个空格分开。)
这个程序很简单:它要一个数字,告诉它用户打算让它计算矩形或是圆形的面积。然后,使用一个if语句(条件执行)来决定应当执行哪个语句块计算面积。这两个语句块同先前面积计算例子中使用的语句块本质上是一样的。留意注释是如何使代码变得更加可读的。编程的第一条戒律就是:“你应当注释!”无论如何——它都是一个应该养成的好习惯。
练习1:
扩展上面的程序使它包括正方形面积的计算,用户只要输入它一条边的长度就可以了。做这个练习之前你需要了解一件事:如果你有两个以上的选择,你可以象这样写:
1 if foo == 1:
2 # Do something...
3 elif foo == 2:
4 # Do something else...
5 elif foo == 3:
6 # If all else fails...
这里的elif是意思为“else if”的神秘代码:)。所以,如foo等于1,做某件事;否则,如果foo等于2,那么做另外的一些事,等等。你也可以在程序中加入其它的选项——象三角形以及任意多边形。随你的便。
- 循环
顺序执行和条件执行仅仅是程序设计三个基本语句块架构方式中的两个。第三个则是循环执行。在上个段落中我假设了一种情况,检查火腿是否烧好,但很明显它并不适用。如果下次检查时火腿仍然没烧好该怎么办?我们怎么知道需要检查多少次?事实上,我们不知道。而且我们也没必要知道。我们可以要求计算机持续检查直到烧好了为止。怎么表达这个?你猜到了——我们使用循环,或者说是重复执行。
python有两种循环类型:while循环和for循环。for循环大概是最简单的。举个例子:
1 for food in "spam", "eggs", "tomatoes":
2 print "I love", food
它的意思是:对于列表”spam”, “eggs”, “tomatoes”中的每个元素,都打印出你喜欢它。循环中的语句块为每个元素执行一次,而且每次执行,当前的元素都被赋给变量food(在这个例子中)。另外一个例子:
1 for number in range(1, 100):
2 print "Hello, world!"
3 print "Just", 100 - number, "more to go..."
4 print "Hello, world"
5 print "That was the last one... Phew!"
函数range返回给定范围的数字列表(包括第一个数字,不包括最后一个……这个例子中是[1……99])。所以,这样解释它:
循环体为1(包括)到100(不包括)之间的数字每个执行一次。(哪个是循环体以及随后的表达式事实上做什么留下来做为练习。)
但这对我们的烧菜问题并没有实质的帮助。如果我们打算检查火腿一百次,那么这是个很好的解决方案;但是我们不知道这是否够——或者太多了。我们只是希望它在温度达不到(或者,直到它足够热——大致某个状态)的时候持续检查。所以,我们使用
while:
1 # Spam-cooking program
2 # Fetch the function sleep
3 from time import sleep
4 print "Please start cooking the spam. (I'll be back in 3 minutes.)"
5 # Wait for 3 minutes (that is, 3*60 seconds)...
6 sleep(180)
7 print "I'm baaack :)"
8 # How hot is hot enough?
9 hot_enough = 50
10 temperature = input("How hot is the spam?")
11 while temperature < hot_enouth:
12 print "Not hot enough... Cook it a bit more..."
13 sleep(30)
14 temperature = input("OK, How hot is it now?")
15 print "It's hot enough - You're done!"
这个例子中的新东西……
1. 有些有用的函数被存储在模块中而且可以被导入。此例中我们从python自带的time模块中导入了函数sleep(它休止给定的多少秒的时间)。(做你自己的模块当然也是可能的……)
练习2:
写一个程序,持续从用户获得数据然后相加,直到它们的和为100。再写一个程序,从用户那里获得100个数据,打印出它们的和。
Bigger Programs - Abstraction
如果想知道一本书的大致内容,你不会翻遍所有的页——你只是看看目录,是不是?它会列出书的主要内容。现在——想像写一本菜谱。许多菜谱,像“奶油火腿通心面”和“瑞士火腿馅饼”很可能包含相同的东西,比如火腿,在这种情况下——你肯定不会打算在每个菜谱里都重复叙述如何制作火腿。(好了……你事实上可能不做火腿……但是为了做例子,请忍受一下:))。你会把制作火腿的菜谱单独放在一个章节,而仅仅在其它章节里引用它。这样——代替在每个菜谱里都完整的描述,你只要引用章节的名称就可以了。在计算机编程中这被称为抽象化。
我们是不是已经象这样运行了某些东西?是的。我们没有详细的告诉计算机如何从用户那里获得一个答案(好了——我们没有真的这样做……同样地……我们也没有真正的在做火腿:))而是简单的使用了input——一个函数来代替。我们事实上可以构造我们自己的函数,来应用于这种类型的抽象化中。
假设我们希望找到小于给定正数的最大整数。例如,给定2.7,这个数应当是2。这往往被称为给定数的“底线(floor)”。(这事实上可以用python的内建函数int来处理,但是,请再次忍受我拿它作例子……)我们该怎样做?一个简单的解决办法是从0开始试每一个可能的数:
number = input(“What is the number?”)
floor = 0
while floor <= number:
floor = floor + 1
floor = floor - 1
print “The floor of “, number, “is “, floor
1
2
3
4
5
6
注意当floor不再小于(或者等于)给定数时循环结束了;我们加了太多1给它。因此我们必须为它减去1。如果我们希望把它应用于完整的数学运算该怎么办呢?我们不得不为求每个数的基数(”floor”-ing)而写一次完整的循环。这很不舒服……你可能猜到了我们代之以什么:把它放在我们自己的函数中,命名为“floor”:
def floor(number):
result = 0
while result <= number:
result = result + 1
result = result - 1
return result
1
2
3
4
5
6
这个例子中的新东西……
1. 函数用关键字def定义,函数名紧随其后并且要用括号把需要的参数括起来。
2. 如果要求函数返回一个值,要使用关键字return来处理(它同时也自动结束函数定义)。
定义了函数之后,我们可以象这样使用它:
x = 2.7
y = floor(2.7)
1
2
执行后,y的值应该是2。定义拥有多个参数的函数也是可以的:
def sum(x, y):
return x + y
1
2
练习3
写一个函数,用欧几里德方法寻找两个数的一个共同因数。工作过程是这样的:
1. 假设两个数,a和b,a大于b
2. 重复以下步骤直到b变成0:
1. a变为b的值
2. b变成没有改变值之前的a除以没有改变值之前的b的余数
3. 返回a的最后一个值
提示:
* 使用a和b作为函数的参数
* 简单的设定a大于b
* x除以z的余数用表达式 x % z 来计算
* 两个变量可以象这样一起赋值:x, y = y, y+1。这里x被赋以值y(这意味着,y的值此前已经指定)而且y被递增了1。
- 深入函数
上面的练习怎么做?难吗?还不太清楚函数?别担心——我还没完成我的话题呢。
我们构建函数时使用的萃取方法称为过程抽象,许多编程语言把关键字过程同函数一样使用。事实上,这两个概念是不一样的,但是在python中它们都被称为函数(因为它们或多或少以同样的方式定义和使用)。
函数和过程(在其它语言中)的区别在哪里呢?嗯——就像你在前面的段落里看到的那样,函数可以返回一个值。区别就是过程并不返回这样的值。许多时候,用这种方法把函数划分为两种类型——返回值的和不返回值的——是很有用的。
不返回值的函数(过程)可以用作子程序或例行程序。我们调用这些函数,它们制造某些原料,就象泡沫鲜奶之类的。我们可以在很多地方使用这个函数而不需要重写它的代码(这被称为代码再利用——以后你还会知道,它意义不仅仅在这里)。
这样的函数(或过程)的另一个有用性体现在——它改变了环境(例如,把糖和奶油混在一起搅拌,它们的整个外部状态就变化了)让我们看个例子:
def hello(who):
print “Hello, “, who
hello(“world”)
Prints out “Hello, world”
1
2
3
4
打印出内容是它一方面的作用,因为这是这个函数唯一需要做的事,它其实是一个典型的所谓过程。但是……它事实上没有改变它的运行环境,是不是?它怎样才能改变呢?让我们试一下:
The wrong way of doing it
age = 0
def setAge(a):
age = a
setAge(100)
print age
Prints “0”
1
2
3
4
5
6
7
错在哪儿?错在函数setAge创建了它自己的也被命名为age的局部变量,它只在setAge函数内部可用。那如何才可以避免出现这个问题呢?我们可以使用全局变量。
注意:全局变量在python中不常用。它们容易引起不好的代码组织结构,被称为意大利面代码。我这里使用它们是为了引出更复杂一点的技术问题——如果你可以请尽量避免使用它们。
通过告诉解释器一个变量是全局的(用象global age这样的表达式做),我们事实上 告诉了它在函数之外使用这个变量,而不是重新创建一个新的局部变量。(所以,和局部相反它是全局的。)因此上面的程序可以象这样重写:
The correct, but not-so-good way of doing it
age=0
def setAge(a):
global age
setAge(100)
print age
Prints “100”
1
2
3
4
5
6
7
了解对象(随后谈到)后,你会发现更好的解决这个问题的办法是使用一个有age属 性和setAge方法的对象。在数据结构那段,你也将会发现一些函数改变它的环境的更好的
例子。
好了——那么真正的函数是什么样?什么是函数呢,事实上?数学函数象一种“机
器”,获得输入然后计算结果。它会每次返回同样的结果,如果每次提供它同样的输入。
例如:
1 def square(x):
2 return x*x
这和数学上的函数f(x)=x*x 一样。它的行为象一个精确的函数,仅仅依赖于它的输入,在任何情况下都不改变它的环境。
所以——我这里描绘了两种构造函数的方法:一种类型更象是过程,不返回任何结 果;另一种更象是数学上的函数,(几乎)什么也不做就是为了返回一个结果。当然,在 这两种极端事物之间做某些事情是可能的,尽管当函数改变事物的时候,它应该清楚它改 变了。你可以通过标记它们的名字区分它们,例如为“纯粹”的函数使用象square这样的名词而对类似过程那样的函数使用象setAge这样命令式的名字。
- 更多类型-数据结构
现在——你已经知道了不少:怎样输入输出,怎样设计复杂的运算法则(程序)来执行数学运算,但是好戏还在后头呢。
截止目前我们都在程序中使用了哪些成份呢?数字和字符串,对不对?没意思的种类……现在让我们引入两三个其它的成份来让事情变得更有意思些。
数据结构是种组织数据的成份。(惊奇,吃惊……)单个的数据没有什么真正的数据结构,是不是?但是假设我们需要很多数放在一起做为一个成份——那就需要某种结构。
例如,我们可能想要一个数据列表。那很容易:
[3, 6, 78, 93]
在循环那段我提到了列表,但没真正描述它。好——这里说的就是你如何创建它。只
需要列出元素,用逗号分开,再加上方括号就行了。
来看一个计算素数(只能被1和它本身整除的数)的
子中的新东西……
内建函数range事实上返回一个列表,可以象所有其它列表那样使用。(它包括第一个数,但是不包括最后一个数。)
列表可以当作逻辑变量使用。如果它非空,则为true,否则为false。因此,while candidates意思是“while名称candidates的列表非空时”或者简单的说“while存在candidates时”。
你可以用if someElement in somelist来检查一个元素是否在列表中。
你可以用someList.remove(someElement)来删除someList中的someElement。
你可以用someList.append(something)为一个列表添加元素。事实上,你也可以使用“+”(象someList = someList+[something])。但是效率不是太高。
你可以通过在列表名之后加上用括号括起来的表示某元素位置的数字(很奇怪,列表的第1个元素,位置是0)来获得列表的某个元素。因此someList[3]是someList 列表的第四个元素(依次类推)。
你可以使用关键字del删除变量。它也可以用来删除列表中的元素(就象这里)。
因此del someList[0]删除someList 列表中的第一个元素。如果删除前列表是[1, 2, 3],删除后就变成了[2, 3]。
在继续叙述索引列表中的元素之前,我简单解释一下上面的例子。
这是古老算术的一个版本,称为“The Sieve of
Erastothenes”(类似这样)。它考量一 系列给定数字(在本例中是一个列表),然后有组织的删除已知不是素数的数字。如何知 道?只要看看它们是不是可以被分解为其它两个数就可以了。
我们从一个包含数字[2…999]的候选列表开始——我们知道1是素数(事实上,它可能是也可能不是,看你问谁了),我们想得到小于1000的所有素数。(事实上,我们的候选列表是[3…999],但是2也是候选数字,因为它是我们的第一base)。
我们还有个叫result的列表,它任何时间都包含着最新的结果。最初的时候,它只包含1。我们还有个叫base的变量。每次循环,我们删除是它的倍数的数字(它总是候选列表中最小数)。每次循环之后,我们知道剩下的最小的数是素数(因为所有可以分解的数我们都删除了)。
因此,我们把它加入result,并把它设为新的base,然后从列表里移除它(这样就不会对 它重复计算了)。当候选列表为空时,result列表将包含所有的素数。精巧吧,哈!
思考一下:第一次循环有什么特别吗?那时base 是2,但它一样经过了筛选。为什 么?为什么这不发生在其它的base值身上?我们打算移除product时能否确定它在候选列 表中呢?为什么?
接下来是什么呢?哦,是的……索引。还有切片。它们是从python列表中获得单个 元素的方法。你已经见到了普通的索引行为。它相当简单。事实上,我已经告诉了你所有 你需要知道的关于它的东西,除了一件事:负数索引从列表的末尾向前计算。所以, someList[-1]是someList的最后一个元素someList[-2]是它之前的一个元素,依次类推。
切片,仍然,对你来说是陌生的。它和索引相似,除了切片可以获得列表中的所有的
元素,而不仅仅是单个的元素。这如何做呢?象这样:
food = [“spam”, “spam”, “eggs”, “sausages”, “spam”]
print food[2:4]
Prints “[‘eggs’, ‘sausages’]”
1
2
3
10. 继续抽象-对象和面向对象编程
现在有个比较热门的词叫做“面向对象编程”。
就象本段标题暗示的那样,面向对象编程仅仅是另外一种抽象细节的方式。程序通过 命名将简单的描述抽象为复杂的操作。在面向对象编程时,我们不仅可以这样对待程序, 还可以把它们做为对象。(现在,这肯定会让你吃惊,哈!)
例如,如果编写烧火腿程 序,我们不用编写很多过程来处理温度、时间、成份等等,我们可以把它们结合为一个火腿对象。或者,也许我们可以再有炉子对象和时钟对象……
那么,象温度这类事物就变成 了火腿对象的一个属性,而时间可以从时钟对象读取。要使用我们的程序做某些事,我们可以教给我们的对象某些方法;比如,炉子应当知道如何烹制火腿等。
那么——在python中我们如何做呢?我们不能直接制造一个对象。不能直接制造一个
炉子,而是做一个菜谱来描述炉子应该是什么样。这份菜谱因此就描述了一个被我们称为
炉子的一类对象。一个非常简单的炉子类可能是这样:
class Oven:
def insertSpam(self, spam):
self.spam = spam
def getSpam(self):
return self.spam
1
2
3
4
5
这看起来很难理解,还是怎样呢?
这个例子中的新东西……
对象的类用关键字class定义。 类的名称通常以大写字母开始,而函数和变量(还有属性和方法)的名称以小写字
母开始。
方法(也就是让对象知道如何去做的函数和操作)的定义没有特别,但是要在类的 定义里面。
所有对象的方法应当有的第一个参数叫做self(或者类似的……)原因很快就清楚
了。
对象的属性和方法可以这样来访问:mySpam.temperature = 2 或者dilbert.be_nice()。
我能猜到上面例子中的某些东西你仍然不清楚。例如,什么是self?还有,现在我们有了对象菜谱(也就是类),我们怎样事实上构造一个对象呢?
我们先颠倒一下顺序。对象通过象引用函数那样引用类名来创建:
1 myOven = Oven()
myOven包含了一个Oven对象,通常叫做Oven类的一个实例。假设我们也构造好了
一个Spam类,那么我们可象这样做:
1 mySpam = Spam()
2 myOven.insertSpam(mySpam)
myOven.spam现在将包含mySpam。怎么回事?因为,我们调用一个对象的某个方法 时,第一个参数,通常称为self,总是包含对象本身。(巧妙,哈!)
这样,self.spam =spam这一行设置当前Oven对象的spam属性的值为参数spam。注意它们是两个不同的事物,尽管在这个例子中它们都被称为spam。
- 练习3答案
def euclid(a, b):
while b:
a, b = b, a%b
re