Thinking in Python
Design Patterns and Problem-Solving Techniques
Bruce Eckel
President, MindView, Inc.
前言
本书中所用的资料开始于我在最近几年中所做的Java联合研讨会,有几次是跟Larry O’Brien, 还有Bill Venners。Bill和我重复了好几次这样的研讨会,并且在这几年中,我们根据对模式新的理解以及召开研讨会的经验,对这些资料进行了数次更改。
在这个过程中,我们都积累了足够多的信息可以来举办自己独立的学术研讨会,我们都不想这么做的一个主要因素是,我们可以从一起举办研讨会这件事情中得到如此多的乐趣。我们曾经在美国的大部份地方举办过这样的讲座,还有布拉格(我们还想尝试每个春天都在那儿举办这样的一个小型讲座,以及很多其它的讲座。)但我们只做了偶尔几次这样的现场讲座,因为我们有2个人,所以花费也较多,并且比较难于统一计划时间表。
非常感谢在这些年中来参加讲座的人们,当然包括Larry与Bill,因为他们帮助我实现这些想法并改进它们。我希望我能够通过这本书跟相关的讲座,在接下来的很多年中继续形成并发展这样的想法。
本书也不会一直停止在这个状态,最初,这些资料是一本C++图书的一部份,然后是Java图书,之后就停止在基于Java的图书上,最后,在长时间的考虑之后,我决定最好的开始来创建我自己在设计模式方面论点集的办法是,首先用Python写下它们(因为我们都知道Python是最理想的原型语言!),之后再将相关的内容转换为Java版本。将想法首先在一个比较强大的语言上进行实践,之后再转换到其它语言,在这方面我也已经积累了一些经验,而且我也发现这样做可以更容易看透问题并保持一个清晰的概念。
所以Thinking in Python这本书,最初的时候,是从Thinking in Patterns for Java移植过来的,而不是一本介绍Python语言的书(书店中有很多针对这个好语言的介绍与学习的书籍)。我认为现在这样的一本书籍会比一本普通的语言教程更令人激动(当然只好向那些希望得到一本语言教程的人说对不起了)。
针对程序员的Python快速教程
本书假定你是一个有经验的程序员,最好你已经通过其它书籍学习过Python语言。对于那些没有学习过Python的人,本章将对此语言给出一个快速介绍。
Python概述
本简介针对有经验的程序员(我想正在读本书的人都会是的)。你可以从www.python.org得到所有的文档信息(特别是A Python Quick Reference部分),也可以阅读很多其它的书籍,如Learning Python,作者是Mark Lutz与David Ascher(O’Reilly, 1999)。
Python经常被当成是一种脚本语言,但是脚本语言通常受限于它们的问题域。Python,从另一个角度来说,是一种支持脚本的编程语言。在这方面它出奇地强大,你可以用它来代替所有的批处理文件,命令行脚本,以及一些简单的程序。但是它远远不至只是一种脚本语言。
Python被设计成非常清晰的书写格式,特别是针对源代码的阅读。你会发现隔很长时间后,你还是可以轻松地阅读与理解这些代码,不管是你自己写的还是其它人写的。这部分得益于简洁,扼要的语法,但是主要的因素的易读性因素是代码的缩进格式—代码段在Python中都通过缩进来标识。例如:
#: c01:if.py
response = "yes"
if response == "yes":
print "affirmative"
val = 1
print "continuing..."
#:~
符号’#’表示从此开始到行尾属于注释部分,就像是C++或Java的’//’符号。
首先注意到在Python中if语句是C风格的。但是在C的if语句中,你必须要用括号来括起条件部分,而在Python中它们是不需要的(如果你用了括号,将不能通过编译)。
条件子句用冒号来结束,这表明紧跟的是一组缩进语句,是if语句中的then部分。在这个例子中是一条print标准输出语句,之后是对一个名字为val的变量的赋值。再往下的代码不再缩进,表示它们不再是if语句的一部分。缩进可以无限嵌套,就像是C++或Java中的大括号,但是在Python中它们不需要也不必要出现,编译器强制所有的代码都按同样的方式进行格式化,这也是Python代码符合可读性的主要原因。
通常情况下,Python中一行只有一条语句(你也可以在一行上放置多行语句,并用分号;来分隔),语句的结尾也不再需要表示结束的分号。即使在上面的小例子中,你也可以看到Python语言被设计成尽可能地简单,并保持可读性。
内建容器
像C++或Java这样的语言,容器都是由附加的类库来提供,而不集成在语言中。在Python中,编程使用的容器在本质上被集成到语言的核心中:List与Array(如Map, Dictionary, Hash-table)都是基本数据类型。这样做在极大程度上提高了语言本身的优雅性。
另外,for语句也被设计为自动在列表中进行迭代,而不是通过一个计数器来循环。这是很有意思的事,因为我们几乎总是使用一个for的步进循环来处理一个数组或容器。Python通过自动使用一个针对序列的迭代器来实现它。下面是一个例子:
#: c01:list.py
list = [ 1, 3, 5, 7, 9, 11 ]
print list
list.append(13)
for x in list:
print x
#:~
第一行创建了一个列表,你可打印这个列表,结果看起来就跟你往列表中赋值的形式一样。List与Java中对应的容器一样—你可以添加新的元素(使用append()),它们会自动调整容器的大小。for语句创建了一个迭代子x用来从列表中取出每一个值。
你也可以通过range()函数来创建一个数字列表,这样你也可以模仿C中的for.
注意到,这里没有任何类型声明—对象名只是简单地出现,Python将会通过你使用它们的方式来推断它们的类型。这也说明Python被设计为你只需要按下那些必需的键来进行编程,你在以前已经被太多的非Python语言要求写下冒号,大括号等等所有的冗长的无用代码,而这些对于描述你的程序想要做什么是没有任何帮助的。
函数
要在Python中创建一个函数,你要使用def关键字,之后是函数名与参数列表,并用一个冒号来开始函数体。下面是第一个函数例子:
#: c01:myFunction.py
def myFunction(response):
val = 0
if response == "yes":
print "affirmative"
val = 1
print "continuing..."
return val
print myFunction("no")
print myFunction("yes")
#:~
注意到在函数签名中没有任何类型信息—它只指定了函数的名字与参数标识符,没有参数类型与返回类型。Python是一种弱类型语言,这说明它只要求最少的打字工作。例如,你可以在同一个函数中传递并返回不同的类型:
#: c01:differentReturns.py
def differentReturns(arg):
if arg == 1:
return "one"
if arg == "one":
return 1
print differentReturns(1)
print differentReturns("one")
#:~
唯一的约束是,对于传入函数的对象,必须要能应用在函数中对它所进行的操作,其它的都不需要关心。在这里,同一个函数对int与string同样都应用了‘+’操作。
#: c01:sum.py
def sum(arg1, arg2):
return arg1 + arg2
print sum(42, 47)
print sum('spam ', "eggs")
#:~
当对string应用操作符’+’时,它意味着连接它们(当然,Python支持操作符重载)。
String
上面的例子介绍了一点点Python操作string的情况,而且是我所见过的语言中做得最好的。你可以用单引号或双引号来表示string,这样做的好处就是,当你用双引号来表示string时,你就可以在string中包含单引号作为普通的字符。
#: c01:strings.py
print "That isn't a horse"
print 'You are not a "Viking"'
print """You're just pounding two
coconut halves together."""
print '''"Oh no!" He exclaimed.
"It's the blemange!"'''
print r'c:/python/lib/utils'
#:~
请注意Python这个名字不是来自于一条蛇,而是一部叫做Monty Python的喜剧。
用三个引号可以括起所有的东西,包括新的行。这就让一些处理特别地方便,如产生Web页面(Python是一种特别好的CGI语言),因为你可以用三引号来括起整个页面而不用其它的编辑。
在string前面的r表示‘原始未加工的’,如对于’/’来说,你就不需要用‘//’来表示一个’/’了。
String中的替换也是特别的简单,因为Python使用C的printf()替换语法,且针对所有的string类型。只要在string后加上’%’,再跟上需要替换的值就行了。
#: c01:stringFormatting.py
val = 47
print "The number is %d" % val
val2 = 63.4
s = "val: %d, val2: %f" % (val, val2)
print s
#:~
就像你在第二个例子中见到的,如果有超过一个参数,你只要简单地用括号括起来就可以了(这实际上构成了一个tuple,一个不能修改的list—当然你也可以用常规的list来处理,只是通常情况下,我们都使用tuple)。
printf()提供的所有格式化功能都是可用的,包括控制小数的位数以及对齐方式等等。Python还提供了非常强大的正则表达式功能。
类
与Python中所有其它东西一样,类的定义只需要使用最少量额外的语法。你可以使用关键字class,在类的主体部分使用def来定义方法。下面是一个简单的例子:
#: c01:SimpleClass.py
class Simple:
def __init__(self, str):
print "Inside the Simple constructor"
self.s = str
# Two methods:
def show(self):
print self.s
def showMsg(self, msg):
print msg + ':',
self.show() # Calling another method
if __name__ == "__main__":
# Create an object:
x = Simple("constructor argument")
x.show()
x.showMsg("A message")
#:~
所有的方法都将self作为他们的第一个参数。在C++与Java中,对于类方法,也都拥有隐藏的第一个参数,他指向方法被调用的对象,并且可以通过this关键字进行访问。Python也使用这样的一个针对当前对象的引用,只是在你声明一个方法时,你必须明确的地将此引用指定为第一个参数。习惯上,这个引用被称作self,但是你可以用任何你想使用的标识符(如果你不使用self,可能会让好多人感到疑惑)。如果你想要引用对象的字段或其它的方法,则必须要在表达式中使用self。然而,当你在调用对象的方法时,如x.show(),你不需要特别处理针对此对象的self引用—这都自动完成了。
在这里,第一个方法是个特殊的方法,就是名称标识符以双下划线开始并结束的方法。在这个情况下,它定义了一个构造器,它在对象创建时自动被调用,跟C++与Java一样。然而,在示例代码的最后部分,你可以看到创建了一个对象,看起来就像是使用类名来调用一个函数。Python简洁的语法可以让你认识到根本不需要像在C++或Java中能看到得新的关键字。
所有在底部的代码都由一个if条件进行调用,此条件检测名为__name__的变量是否等于__main__。再强调一次,有双下划线出现的地方就表示一个特殊的名字。使用此if语句的原因是任何文件都可以被当成一个库由其它一个程序调用(很快将会讲到模块)。在这种情况下,你只是想使用定义好的类,并不想执行在文件底部的代码。这个特殊的语句只有在你直接执行此文件的情况下才为真,就像下面这样在命令行上输入:
Python SimpleClass.py
然而,若这个文件由另一个程序作为一个模块引入,则__main__代码就不会被执行。
最初,你会感到有些比较意外的东西,就是你只在类方法中定义字段,而不像C++或Java中那样在方法之外定义(若你使用C++/Java方式来定义字段,它们就会隐含地成为static字段)。要创建一个对象字段,你只要命名它—使用self引用—只任何一个方法中(通常是在构造函数中,但并不总是),当这个方法被调用后,相应的内存空间就被创建出来了。这跟C++/Java相比看起来有点奇怪,在它们中,你必须首先决定你的对象需要占用多少空间,但现在,这种新的方法为编程提供了一个非常灵活的结果。
继承
因为Python是弱类型语言,所以它不会关心接口—所有它关心的只有应用到对象上操作(实际上,Java的interface关键字在Python中是多余的)。这意味着在Python中继承与C++/Java中通过继承来建立一个公共接口的目的是不同的。在Python中,继承唯一的理由就是继承一个实现—那重用基类中的代码。
如果你准备继承一个类,你必须告诉Python将这个类带入你的新文件中。Python使用与Java类似的方式来控制它的名字空间。只要你创建一个文件,你就隐含地创建了一个与文件同名的模块(就像是Java中的一个包),因而,在Python中不需要package关键字。当你想要使用一个模块时,你只要使用import,并给出模块的名字。Python将会搜索PYTHONPATH,就像Java会搜索CLASSPATH一样(但是由于一些原则,Python不会有Java中的一些缺陷),并读入此模块。要访问一个模块中的函数或类,你只要给出模块名,一个’.’,再加上函数或类的名字。如果你不想被名字限制符所困扰,你可以给出:
from module import name(s)
这里name(s)可以是用逗号分隔的一组名字。
你可以通过在类名后的括号中列出类(或一组类,Python支持多重继承)的名字来实现继承。同时,请注意Simple类,它存在于名字为SimpleClass的文件(也就是模块)中,通过import被带入到新的名字空间中来。
#: c01:Simple2.py
from SimpleClass import Simple
class Simple2(Simple):
def __init__(self, str):
print "Inside Simple2 constructor"
# You must explicitly call
# the base-class constructor:
Simple.__init__(self, str)
def display(self):
self.showMsg("Called from display()")
# Overriding a base-class method
def show(self):
print "Overridden show() method"
# Calling a base-class method from inside
# the overridden method:
Simple.show(self)
class Different:
def show(self):
print "Not derived from Simple"
if __name__ == "__main__":
x = Simple2("Simple2 constructor argument")
x.display()
x.show()
x.showMsg("Inside main")
def f(obj): obj.show() # One-line definition
f(x)
f(Different())
#:~
Simple2从Simple中继承得到,在构造器中,调用了基类的构造器。在display()方法中,showMsg()可以被作为self的一个方法进行调用,但是在你调用被覆盖的基类方法时,你必须要使用带限制符的函数名,并将self作为第一个参数,就像调用基类构造器时所做的那样,在覆盖版本的show()方法中也调用了基类的show()方法。
在__main__中,运行程序,你将会看到基类构造器被调用的过程,同时也可以看到继承类中的showMsg()方法被调用,一切都跟我们期望的继承行卫一致。
The class Different also has a method named show( ), but this class is not derived from Simple. The f( ) method defined in __main__ demonstrates weak typing: all it cares about is that show( ) can be applied to obj, and it doesn’t have any other type requirements. You can see that f( ) can be applied equally to an object of a class derived from Simple and one that isn’t, without discrimination. If you’re a C++ programmer, you should see that the objective of the C++ template feature is exactly this: to provide weak typing in a strongly-typed language. Thus, in Python you automatically get the equivalent of templates – without having to learn that particularly difficult syntax and semantics. Add Comment