预计阅读本页时间:-
看过Python面向过程的编程范式之后,我们在这一章将使用一种完全不同的编程范式——面向对象(Objected-Oriented)。Python不只是一门支持面向对象范式的语言。在多范式的外表下,Python用对象来构建它的大框架。因此,我们可以及早切入面向对象编程,从而了解Python的深层魅力。
4.1 轻松看对象
1.面向对象语言的来历
要想了解面向对象,就要先来了解类(Class)和对象(Object)。还记得面向过程中的函数和模块吗,它们提高了程序的可复用性。类和对象同样提高了程序的可复用性。除此之外,类和对象这两种语法结构还加强了程序模拟真实世界的能力。“模拟”,正是面向对象编程的核心。
面向对象范式可以追溯到Simula语言。克利斯登·奈加特是这门语言的两位作者之一。他被挪威国防部征召入伍,然后服务于挪威防务科学研究所。作为一名训练有素的数学家,克利斯登·奈加特一直在用电脑解决国防中的计算问题,例如核反应堆建设、舰队补给、后勤供应等。在解决这些问题的过程中,奈加特需要用电脑来模拟出真实世界的状况。比如说,如果发生一次核泄漏,会造成怎样的影响。奈加特发现,按照之前过程式的、指令式的编程方式,他很难用程序来表示真实世界中的个体。就拿一艘船来说,我们知道它会有一些数据,如高度、宽度、马力、吃水量等。它还会有一些动作,如移动、加速、加油、停泊等。这艘船就是一个个体。有些个体可以划为一类,如战列舰和航母都是军舰。有些个体之间有着包含关系,如一条船有船锚。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
当人们讲故事时,会自然而然地描述来自真实世界的个体。但对于只懂0/1序列的计算机来说,它只会机械地执行一条条指令。奈加特希望,当他想要用计算机做模拟时,能像讲故事一样简单。他凭着自己在军事和民用方面的经验,知道这样的一种编程语言有着巨大的潜力。最终,他遇到了计算机专家奥利-约翰·达尔。达尔帮助奈加特把他的想法变成一门新颖的语言—Simula。这门语言的名字,正是奈加特朝思暮想的“模拟”(1)。
我们可以把面向对象看作是故事和指令之间的桥梁。程序员用一种故事式的编程语言描述问题,随后编译器会把这些程序翻译成机器指令。但在计算机发展的早期,这些额外的翻译工作会消耗太多的计算机资源。因此,面向对象的编程范式并不流行。一些纯粹的面向对象语言,也经常因为效率低下而受到诟病。
随着计算机性能的提高,效率问题不再是瓶颈。人们转而关注程序员的产量,开始发掘面向对象语言的潜力。在面向对象领域最先取得辉煌成功的是C++语言。比雅尼·斯特劳斯特鲁普在C语言的基础上增加面向对象的语法结构,创造出C++语言。C++杂糅了C语言特征,所以显得异常复杂。后来的Java语言向着更纯粹的面向对象范式靠拢,很快获得了商业上的成功。C++和Java一度成为最流行的编程语言。后来微软推出的C#语言,以及苹果一直在支持的Objective-C语言,也都是典型的面向对象语言。
Python也是一门面向对象语言。它比Java还要历史悠久。只不过,Python允许程序员以纯粹的面向过程的方式来使用它,所以人们有时会忽视它那颗面向对象的心。Python的一条哲学理念是“一切皆对象”。无论是我们第3章看到的面向过程范式,还是未来会看到的函数式编程,其实都是特殊的对象模拟出的效果。因此,学习面向对象是学Python的一个关键环节。只有了解了Python的对象,我们才能看到这门语言的全貌。
2.类
说是要“找对象”,我们第一个看的却是个叫作“类”的语法结构。这里的类其实和我们日常生活中的“类”的概念差不多。日常生活中,我们把相近的东西归为一类,而且给这个类起一个名字。比如说,鸟类的共同属性是有羽毛,通过产卵生育后代。任何一只特别的鸟都是建立在鸟类的原型基础上的。
下面我们用Python语言来记录上面的想法,描述鸟类:
class Bird(object): feather = True reproduction = "egg"
在这里,我们用关键字class来定义一个类。类的名字就是鸟(Bird)。括号里有一个关键词object,也就是“东西”的意思,即某一个个体。在计算机语言中,我们把个体称为对象。一个类别下,可以有多个个体。鸟类就可以包括邻居老王养的金丝雀、天边正飞过的那只乌鸦,以及家里养的一只小黄鸡。
冒号和缩进说明了属于这个类的代码。在隶属于这个类别的程序块中,我们定义了两个量,一个用于说明鸟类有羽毛(feather),另一个用于说明鸟类的繁殖方式(reproduction),这两个量称为类的属性(attribute)。我们定义鸟类的方法很粗糙,鸟类只不过是“有毛能产蛋”的东西。要是生物学家看到了大概会暗自摇头,但我们毕竟迈出了模拟世界的第一步。
我们除了用数据性的属性来分辨类别外,有时也会根据这类东西能做什么事情来区分。比如说,鸟会移动。这样,鸟就和房屋的类别就区分开了。这些动作会带来一定的结果,比如移动导致位置的变化。这样的一些“行为”属性称为方法(method)。Python中,一般通过在类的内部定义函数来说明方法。
class Bird(object): feather = True reproduction = "egg" def chirp(self, sound): print(sound)
我们给鸟类新增一个方法属性,就是表示鸟叫的方法chirp()。方法chirp()看起来很像一个函数。它的第一个参数是self,是为了在方法内部引用对象自身,我将在后面详细解释。需要强调的是,无论该参数是否用到,方法的第一个参数必须是用于指代对象自身的self。剩下的参数sound是为了满足我们的需求设计的,它代表了鸟叫的内容。方法chirp()会把sound打印出来。
3.对象
我们定义了类,但和函数定义一样,这还只是打造兵器的过程。为了使用这个利器,我们需要深入到对象的层面。通过调用类,我们可以创造出这个类下面的一个对象。比如说,我养了一只小鸡,叫summer。它是个对象,且属于鸟类。我们使用前面已经定义好的鸟类,产生这个对象:
summer = Bird()
通过这一句创建对象,并说明summer是属于鸟类的一个对象。现在,我们就可以使用鸟类中已经写好的代码了。作为对象的summer将拥有鸟类的属性和方法。对属性的引用是通过对象.属性(object.attribute)的形式实现的。比如说:
print(summer.reproduction) # 打印'egg'
用上面的方式,我们得到summer所属类的繁殖方式。
此外,我们还可以调用方法,让summer执行鸟类允许的动作。比如:
summer.chirp("jijiji") # 打印'jijiji'
在调用方法时,我们只传递了一个参数,也就是字符串"jijiji"。这正是方法与函数有所区别的地方。尽管在定义类的方法时,我们必须加上这个self参数,但self只用能在类定义的内部,所以在调用方法时不需要对self传入数据。通过调用chirp()方法,我的summer就可以叫了。
到现在为止,描述对象的数据都存储于类的属性中。类属性描述了一个类的共性,比如鸟类都有羽毛。所有属于该类的对象会共享这些属性。比如说,summer是鸟类的一个对象,因此summer也有羽毛。当然,我们可以通过某个对象来引用某个类属性。
对于一个类下的全部个体来说,某些属性可能存在个体差异。比如说,我的summer是黄色的,但并非所有的鸟儿都是黄色的。再比如说人这个类。性别是某个人的一个性质,不是所有的人类都是男,或者都是女。这个性质的值随着对象的不同而不同。李雷是人类的一个对象,性别是男。韩美美也是人类的一个对象,性别是女。
因此,为了完整描述个体,除了共性的类属性外,我们还需要用于说明个性的对象属性。在类中,我们可以通过self来操作对象的属性。现在我们拓展Bird类:
class Bird(object): def chirp(self, sound): print(sound) def set_color(self, color): self.color = color summer = Bird() summer.set_color("yellow") print(summer.color) # 打印'yellow'
在方法set_color()中,我们通过self参数设定了对象的属性color。和类属性一样,我们能通过对象.属性的方式来操作对象属性。由于对象属性依赖于self,所以我们必须在某个方法内部才能操作类属性。因此,对象属性没办法像类属性一样,在类下方直接赋初值。
但Python还是提供了初始化对象属性的办法。Python定义了一系列特殊方法。特殊方法又被称为魔法方法(Magic Method)。特殊方法的方法名很特别,前后有两个下画线,比如__init__()、__add__()、__dict__()等。程序员可以在类定义中设定特殊方法。Python会以特定的方式来处理各个特殊方法。对于类的__init__()方法,Python会在每次创建对象时自动调用。因此,我们可以在__init__()方法内部来初始化对象属性:
class Bird(object): def __init__(self, sound): self.sound = sound print("my sound is:", sound) def chirp(self): print(self.sound) summer = Bird("ji") summer.chirp() # 打印'ji'
在上面的类定义中,我们通过__init__()方法说明了这个类的初始化方式。每当对象建立时,比如创建summer对象时,__init__()方法就会被调用。它会设定sound这个对象属性。在后面的chirp()方法中,就可以通过self调用这一对象属性。除了设定对象属性外,我们还可以在__init__()中加入其他指令。这些指令会在创建对象时执行。在调用类时,类的后面可以跟一个参数列表。这里放入的数据将传给__init__()的参数。通过__init__()方法,我们可以在创建对象时就初始化对象属性。
除了操作对象属性外,self参数还有另外一个功能,就是能让我们在一个方法内部调用同一类的其他方法,比如:
class Bird(object): def chirp(self, sound): print(sound) def chirp_repeat(self, sound, n): for i in range(n): self.chirp(sound) summer = Bird() summer.chirp_repeat("ji", 10) # 重复打印'ji'10次
在方法chirp_repeat()中,我们通过self调用了类中的另一个方法chirp()。
4.2 继承者们
1.子类
类别本身还可以进一步细分成子类。比如说,鸟类可以进一步分成鸡、天鹅。在面向对象编程中,我们通过继承(Inheritance)来表达上述概念。
class Bird(object): feather = True reproduction = "egg" def chirp(self, sound): print(sound) class Chicken(Bird): how_to_move= "walk" edible = True class Swan(Bird): how_to_move= "swim" edible = False summer = Chicken() print(summer.feather) # 打印True summer.chirp("ji") # 打印'ji'
新定义的鸡(Chicken)类,增加了两个属性:移动方式(how_to_move)和可以食用(edible)
在类定义时,括号里为Bird。这说明,鸡类是属于鸟类(Bird)的一个子类,即Chicken继承自Bird。自然而然,鸟类就是鸡类的父类。Chicken将享有Bird的所有属性。尽管我们只声明了summer是鸡类,但它通过继承享有了父类的属性,比如数据性的属性feather,还有方法性的属性chirp()。新定义的天鹅(Swan)类,同样继承自鸟类。在创建一个天鹅对象时,该对象自动拥有鸟类的属性。

图4-1 一个鸡类的对象
很明显,我们可以通过继承来减少程序中的重复信息和重复语句。如果我们分别定义鸡类和天鹅类,而不是继承自鸟类,就必须把鸟类的属性分别输入到鸡类和天鹅类的定义中。整个过程会变得烦琐,因此,继承提高了程序的可重复使用性。最基础的情况,是类定义的括号中是object。类object其实是Python中的一个内置类。它充当了所有类的祖先。
分类往往是人了解世界的第一步。我们将各种各样的东西分类,从而了解世界。从人类祖先开始,我们就在分类。18世纪是航海大发现的时代,欧洲航海家前往世界各地,带回来闻所未闻的动植物标本。人们激动于大量出现的新物种,但也头痛于如何分类。卡尔·林奈提出一个分类系统,通过父类和子类的隶属关系,为进一步的科学发现铺平了道路。面向对象语言及其继承机制,正是模拟人的有意识分类过程。
2.属性覆盖
如上所述,在继承的过程中,我们可以在子类中增加父类不存在的属性,从而增强子类的功能。此外,我们还可以在子类中替换父类已经存在了的属性,比如:
class Bird(object): def chirp(self): print("make sound") class Chicken(Bird): def chirp(self): print("ji") bird = Bird() bird.chirp() # 打印'make sound' summer = Chicken() summer.chirp() # 打印'make sound'和'ji'
鸡类(Chicken)是鸟类(Bird)的子类。在鸡类(Chicken)中,我们定义了方法chirp()。这个方法在鸟类中也有定义。通过调用可以看出,鸡类会调用自身定义的 chirp()方法,而不是父类中的chirp()方法。从效果上看,这就好像父类中的方法chirp()被子类中的同名属性覆盖(override)了一样。
通过对方法的覆盖,我们可以彻底地改变子类的行为。但有的时候,子类的行为是父类行为的拓展。这时,我们可以通过super关键字在子类中调用父类中被覆盖的方法,比如:
class Bird(object): def chirp(self): print("make sound") class Chicken(Bird): def chirp(self): super().chirp() print("ji") bird = Bird() bird.chirp() # 打印"make sound" summer = Chicken() summer.chirp() # 打印"make sound"和"ji"
在鸡类的chirp()方法中,我们使用了super。它是一个内置类,能产生一个指代父类的对象。通过super,我们在子类的同名方法中调用了父类的方法。这样,子类的方法既能执行父类中的相关操作,又能定义属于自己的额外操作。
调用super的语句可以出现在子类方法的第一句,也可以出现在子类方法的任意其他位置。
4.3 那些年,错过的对象
1.列表对象
我们从最初的"Hello World!",一路狂奔到对象面前。俗话说,人生就像一场旅行,重要的是沿途的风景。事实上,前面几章已经多次出现对象。只不过,那时候没有引入对象的概念,所以只能遗憾错过。是时候回头看看,我们错过的那些对象了。
我们先来看一个熟人,数据容器中的列表。它是一个类,用内置函数可以找到类的名字:
>>>a = [1, 2, 5, 3, 5] >>>type(a)
根据返回的结果,我们知道a属于list类型,也就是列表类型。其实,所谓的类型就是对象所属的类的名字。每个列表都属于这个list类。这个类是Python自带的,已经提前定义好的,所以称为内置类。当我们新建一个表时,实际上是在创建list类的一个对象。我们还可以用其他两个内置函数来进一步调查类的信息:dir()和help()。函数dir()用来查询一个类或者对象的所有属性。你可以尝试一下:
>>>dir(list)
我们已经用help()函数来查询了函数的说明文档。它还可以用于显示类的说明文档。你可以尝试一下:
>>>help(list)
返回的不但有关于list类的描述,还简略说明了它的各个属性。顺便提一下,制作类的说明文档的方式,与制作函数说明文档类似,我们只需在类定义下用多行字符串加入自己想要的说明就可以了:
class HelpDemo(object): """ This is a demo for using help() on a class """ pass print(help(HelpDemo))
程序中的pass是Python的一个特殊关键字,用于说明在该语法结构中“什么都不做”。这个关键字保持了程序结构的完整性。
通过上面的查询,我们看到类还有许多“隐藏技能”。比如下面一些list的方法,可以返回列表的信息:
>>>a = [1, 2, 3, 5, 9.0, "Good", -1, True, False, "Bye"] >>>a.count(5) # 计数,看总共有多少个元素5 >>>a.index(3) # 查询元素3第一次出现时的下标
有些方法还允许我们对列表进行修改操作:
>>>a.append(6) # 在列表的最后增添一个新元素6 >>>a.sort() # 排序 >>>a.reverse() # 颠倒次序 >>>a.pop() # 去除最后一个元素,并将该元素返回。 >>>a.remove(2) # 去除第一次出现的元素2 >>>a.insert(0,9) # 在下标为0的位置插入9 >>>a.clear() # 清空列表
通过对方法的调用,列表的功能大为增强。再次从对象的角度来认识列表,感觉就像一次美好的聚会。
2.元组与字符串对象
元组与列表一样,都是序列。但元组不能变更内容。因此,元组只能进行查询操作,不能进行修改操作:
>>>a = (1, 3, 5) >>>a.count(5) # 计数,看总共有多少个元素5 >>>a.index(3) # 查询元素3第一次出现时的下标
字符串是特殊的元组,因此可以执行元组的方法:
>>>a="abc" >>>a.index("c")
尽管字符串是元组的一种,但字符串(string)有一些方法能改变字符串。这听起来似乎违背了元组的不可变性。其实,这些方法并不是修改字符串对象,而是删除原有字符串,再建立一个新的字符串,所以并没有违背元组的不可变性。
下面总结了字符串对象的方法。str为一个字符串,sub为str的一个子字符串。s为一个序列,它的元素都是字符串。width为一个整数,用于说明新生成字符串的宽度。这些方法经常用于字符串的处理。
>>>str = "Hello World!" >>>sub = "World" >>>str.count(sub) # 返回:sub在str中出现的次数 >>>str.find(sub) # 返回:从左开始,查找sub在str中第一次出现的位置。 #如果str中不包含sub,返回 -1 >>>str.index(sub) # 返回:从左开始,查找sub在str中第一次出现的位置。 #如果str中不包含sub,举出错误 >>>str.rfind(sub) # 返回:从右开始,查找sub在str中第一次出现的位置 #如果str中不包含sub,返回 -1 >>>str.rindex(sub) # 返回:从右开始,查找sub在str中第一次出现的位置 # 如果str中不包含sub,举出错误 >>>str.isalnum() # 返回:True,如果所有的字符都是字母或数字 >>>str.isalpha() # 返回:True,如果所有的字符都是字母 >>>str.isdigit() # 返回:True,如果所有的字符都是数字 >>>str.istitle() # 返回:True,如果所有的词的首字母都是大写 >>>str.isspace() # 返回:True,如果所有的字符都是空格 >>>str.islower() # 返回:True,如果所有的字符都是小写字母 >>>str.isupper() # 返回:True,如果所有的字符都是大写字母 >>>str.split([sep, [max]]) # 返回:从左开始,以空格为分隔符(separator),# 将str分割为多个子字符串,总共分割max次。# 将所得的子字符串放在一个表中返回。可以以 # str.split(",")的方式使用其他分隔符 >>>str.rsplit([sep, [max]]) # 返回:从右开始,以空格为分隔符(separator),# 将str分割为多个子字符串,总共分割max次。# 将所得的子字符串放在一个表中返回。可以以 # str.rsplit(",")的方式使用其他分隔符 >>>str.join(s) # 返回:将s中的元素,以str为分隔符, # 合并成为一个字符串。 >>>str.strip([sub]) # 返回:去掉字符串开头和结尾的空格。 # 也可以提供参数sub,去掉位于字符串开头和结尾的sub >>>str.replace(sub, new_sub) # 返回:用一个新的字符串new_sub替换str中 # 的sub >>>str.capitalize() # 返回:将str第一个字母大写 >>>str.lower() # 返回:将str全部字母改为小写 >>>str.upper() # 返回:将str全部字母改为大写 >>>str.swapcase() # 返回:将str大写字母改为小写,小写字母改为大写 >>>str.title() # 返回:将str的每个词(以空格分隔)的首字母# 大写 >>>str.center(width) # 返回:长度为width的字符串,将原字符串放入# 该字符串中心,其他空余位置为空格。 >>>str.ljust(width) # 返回:长度为width的字符串,将原字符串左对# 齐放入该字符串,其他空余位置为空格。 >>>str.rjust(width) # 返回:长度为width的字符串,将原字符串右对齐放入# 该字符串,其他空余位置为空格。
3.词典对象
词典同样是一个类:
>>>example_dict = {"a":1, "b":2} >>>type(example_dict)
我们可以通过词典的keys()方法,来循环遍历每个元素的键:
for k in example_dict.keys(): print(example_dict[k])
通过values()方法,可以遍历每个元素的值。或者用items方法,直接遍历每个元素:
for v in example_dict.values(): print(v) for k,v in example_dict.items(): print(k, v)
我们也可以用clear()方法,清空整个词典:
example_dict.clear() # 清空example_dict,example_dict变为{}
4.4 意想不到的对象
1.循环对象
Python中的许多语法结构都是由对象实现的,循环就可以通过对象实现。循环对象并不是在Python诞生之初就存在的,但它的发展极为迅速,特别是在Python 3时代,循环对象正在成为循环的标准形式。
那么,什么是循环对象呢?所谓的循环对象包含有一个__next__()方法(2)。这个方法的目的是生成循环的下一个结果。在生成过循环的所有结果之后,该方法将抛出StopIteration异常。
当一个像for这样的循环语法调用循环对象时,它会在每次循环的时候调用__next__()方法,直到StopIteration出现。循环接收到这个异常,就会知道循环已经结束,将停止调用__next__()。
我们用内置函数iter()把一个列表转变为循环对象。这个循环对象将拥有__next__()方法。我们多次调用__next__()方法,将不断返回列表的值,直到出现异常:
>>>example_iter = iter([1, 2]) >>>example_iter.__next__() # 显示1 >>>example_iter.__next__() # 显示2 >>>example_iter.__next__() # 出现StopIteration异常。
我们上面重复调用__next__()的过程,就相当于手动进行了循环。我们可以把循环对象包裹在for中自动进行循环:
for itemin iter([1, 2]): print(item)
在这里,for结构自动调用__next__()方法,将该方法的返回值赋予给item。循环知道出现StopIteration的时候结束。当然,我们可以省去内置函数iter的转换。这是因为,for结构会自动执行这一转换(3)。
相对于序列,循环对象的好处在于:不用在循环还没开始的时候,就生成要使用的元素。所有要使用的元素可以在循环过程中逐渐生成。这样,不仅节省了空间,提高了效率,还会使编程更加灵活。
我们可以借助生成器(generator)来自定义循环对象。生成器的编写方法和函数定义类似,只是在return的地方改为yield。生成器中可以有多个yield。当生成器遇到一个yield时,会暂停运行生成器,返回yield后面的值。当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个yield。生成器自身又构成一个循环对象,每次循环使用一个yield返回的值。
下面是一个生成器:
def gen():
a = 100
yield a
a = a*8
yield a
yield 1000
该生成器共有三个yield,如果用作循环对象时,会进行三次循环。
for i in gen(): print(i)
再考虑下面一个生成器:
def gen(): i = 0 while i < 10000000: i = i + 1 yield i
这个生成器能产生10 000 000个元素。如果先创建序列保存这10 000 000个元素,再循环遍历,那么这个序列将占用大量的空间。出于同样的原因,Python中的内置函数range()返回的是一个循环对象,而不是一个序列(4)。
2.函数对象
前面说过,在Python中,函数也是一种对象。实际上,任何一个有__call__()特殊方法的对象都被当作是函数。比如下面的例子:
class SampleMore(object): def __call__(self, a): return a + 5 add_five = SampleMore() # 生成函数对象 print(add_five(2)) #像一个函数一样调用函数对象,结果为7。
add_five为SampleMore类的一个对象,当被调用时,add_five执行加5的操作。
我们将在第7章中深入研究函数对象。
3.模块对象
前面说过,Python中的模块对应一个.py文件。模块也是对象。比如,我们直接引入标准库中的模块time:
import time print(dir(time))
可以看到,time有很多属性可以调用,例如sleep()方法。我们之前用import语句引入其他文件中定义的函数,实际上就是引入模块对象的属性,比如:
from time import sleep sleep(10) print("wake up")
模块time的sleep()会中止程序。调用时的参数说明给了中止的时间。我们还可以用简单暴力的方法,一次性引入模块的所有属性:
from time import *
sleep(10)
既然知道了sleep()是time的一个方法,那么我们当然可以利用对象.属性的方式来调用它。
import time time.sleep(10)
我们在调用方法时附带上了对象名。这样做的好处是可以拓展程序的命名空间,避免同名冲突。例如,如果两个模块中都有sleep()方法,那么我们可以通过不一样的模块名来区分开来。在my_time.py中写入函数:
def sleep(self): print("I am sleeping.")
在main.py中引入内置模块time和自定义模块my_time:
import time import my_time time.sleep() my_time.sleep()
上面的两次对sleep()方法的调用中,我们通过对象名区分出了不同的sleep()。
在引入模块时,我们还可以给模块换个名字:
import time as t t.sleep(10)
在引入名字较长的模块时,这个换名字的办法能有效地挽救程序员的手指。
可以将功能相似的模块放在同一个文件夹中,构成一个模块包。比如放在this_dir中:
import this_dir.module
引入this_dir文件夹中的module模块。
该文件夹中必须包含一个__init__.py的文件,提醒Python,该文件夹为一个模块包。__init__.py可以是一个空文件。
每个模块对象都有一个__name__属性,用来记录模块的名字,例如:
import time print(time.__name__)
当一个.py文件作为主程序运行时,比如python foo.py,这个文件也会有一个对应的模块对象。但这个模块对象的__name__属性会是"__main__"。因此,我们在很多.py文件中可以看到下面的语句:
if __name__ == "__main__": ...
它的意思是说,如果这个文件作为一个主程序运行,那么将执行下面的操作。有的时候,一个.py文件中同时有类和对象的定义,以及对它们的调用。当这些.py文件作为库引入时,我们可能并不希望执行这些调用。通过把调用语句放到上面的if中,就可以在调用时不执行这些调用语句了。
4.异常对象
前面我们提到过,可以在程序中加入异常处理的try结构,捕捉程序中出现的异常。实际上,我们捕捉到的也是一个对象,比如:
try: m = 1/0 except ZeroDivisionError as e: print("Catch NameError in the sub-function") print(type(e)) # 类型为"exceptions.ZeroDivisionError" print(dir(e)) # 异常对象的属性 print(e.message) # 异常信息integer division or modulo by zero
利用except… as…的语法,我们在except结果中用e来代表捕获到的类型对象。关键字except直接跟随的ZeroDivisionError实际上是异常对象的类。正因为如此,我们在举出异常时会创建一个异常对象:
raise ZeroDivisionError()
在Python中,循环、函数、模块、异常都是某种对象。当然,我们可以完全按照面向过程中的方式来调用这些语法,而不必关注它们底层的对象模型。但出于学习的目的,这些语法结构的对象模型能加深我们对Python的理解。
附录A 代码规范
类的命名采用首字母大写的英文单词。如果由多个单词连接而成,则每个单词的首字母都大写。单词之间不出现下画线。
对象名、属性名和方法名,全部用小写字母。单词之间用下画线连接。
————————————————————
(1) “模拟”的英文是Simulation。
(2) 在Python 2.7中,__next__()方法的名字是next()方法。
(3) 在Python 2.7的for循环中,Python直接从序列出对象,序列不会转换成循环对象。
(4) 在Python 2.7中,range()返回的确实是一个序列。