python from entry to abandon3
第十章的内容是解决问题————编写一个Python脚本。在我的电脑上因为Zip命令不能正常工作所以无法给出演示。该章给出了很有意义的编程思路,对以后学习和工作都有不错的参考意义,这部分有兴趣的同学还是自己去看原版教程吧。
这篇博客是来个给薄薄的《简明Python教程》做收尾工作的,会把第十一章到第十六章的内容按照我自己的理解来总结整理起来。这些内容主要包括以下部分:
11.面向对象的编程
12.输入/输出
13.异常
14.Python标准库
15.更多Python的内容
16.接下来学习什么?
11.面向对象编程
简介:
到目前为止,在我们的程序中,我们都是根据操作数据的函数或语句块来设计程序的。这被称 为面向过程的编程。还有一种把数据和功能结合起来,用称为对象的东西包裹起来组织程序 的方法。这种方法称为面向对象的编程理念。在大多数时候你可以使用过程性编程,但是有些时候当你想要编写大型程序或是寻求一个更加合适的解决方案的时候,你就得使用面向对象 的编程技术。
类和对象是面向对象编程的两个主要方面。类创建一个新类型,而对象这个类的 实例。这类似于你有一个int类型的变量,这存储整数的变量是int类的实例(对象)。
对象可以使用普通的属于对象的变量存储数据。属于一个对象或类的变量被称为域。对象也可以使用属于类的函数来具有功能。这样的函数被称为类的方法。这些术语帮助我们把它们与孤立的函数和变量区分开来。域和方法可以合称为类的属性。
域有两种类型——属于每个实例/类的对象或属于类本身。它们分别被称为实例变量和
类变量。
类使用class关键字创建。类的域和方法被列在一个缩进块中。
关键字:面向过程的编程 面向对象的编程 类 对象 域 方法 class
self:
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称,但是在调用这个方法的时候你不为这个参数赋值,Python会提供这个值。这个特别的变量指对象本身,按照惯例它的名称是self。
虽然你可以给这个参数任何名称,但是强烈建议你使用self这个名称——其他名称都是不赞成 你使用的。使用一个标准的名称有很多优点——你的程序读者可以迅速识别它,如果使用self 的话,还有些IDE(集成开发环境)也可以帮助你。
你一定很奇怪Python如何给self赋值以及为何你不需要给它赋值。举一个例子会使此变得清晰。假如你有一个类称为MyClass和这个类的一个实例MyObject。当你调用这个对象的方法 MyObject.method(arg1, arg2)的时候,这会由Python自动转为MyClass.method(MyObject, arg1, arg2)——这就是self的原理了。
这也意味着如果你有一个不需要参数的方法,你还是得给这个方法定义一个self参数 。
关键字:区别 额外的第一个参数名称 self 指对象本身 不用赋值
类:
创建一个类
例11.1
#!/usr/bin/python
#Filename:simplestclass.py
class person:
pass
p=person()
print p
输出
# python simplestclass.py
<__main__.person instance at 0x7ff1cc9121b8>
如何工作:
我们使用class语句后跟类名,创建了一个新的类。这后面跟着一个缩进的语句块形成类体。在 这个例子中,我们使用了一个空白块,它由pass语句表示。
接下来,我们使用类名后跟一对圆括号来创建一个对象(实例)。为了验证,我们简单地打印了这个变量的类型。它告诉我们我们已经在__main__模块中有了一个Person类的实例。
对象的方法:
我们已经讨论了类或者对象可以拥有像函数一样的方法,这些方法与函数的区别只是一个额外的 self变量。
使用对象的方法
例11.2
#!/usr/bin/python
#Filename:method.py
class person:
def sayHi(self):
print 'Hello,how are you?'
p=person()
p.sayHi()
输出:
# python method.py
Hello,how are you?
如何工作:
这里我们看到了self的用法。注意sayHi方法没有任何参数,但仍然在函数定义时有self。
__init__方法
在Python的类中有很多方法的名字有特殊的重要意义。现在我们将学习__init__方法的意义。
__init__方法在类的一个对象被建立时,马上运行。这个方法可以用来对你的对象做一些你希 望的 初始化 。注意,这个名称的开始和结尾都是双下划线。
使用__init__方法
例11.3
#!/usr/bin/python
#Filename:class_init.py
class person:
def __init__(self,name):
self.name=name
def sayHi(self):
print 'Hello.my name is',self.name
p=person('Swaroop')
p.sayHi()
输出:
# python class_init.py
Hello,my name is Swaroop
如何工作:
这里,我们把__init__方法定义为取一个参数name(以及普通的参数self)。在这个__init__里, 我们只是创建一个新的域,也称为name。注意它们是两个不同的变量,尽管它们有相同的名字。点号使我们能够区分它们。
最重要的是,我们没有专门调用__init__方法,只是在创建一个类的新实例的时候,把参数包 括在圆括号内跟在类名后面,从而传递给__init__方法。这是这种方法的重要之处。
现在,我们能够在我们的方法中使用self.name域。这在sayHi方法中得到了验证。
注:事先定义好__init__方法,不需要专门调用就能完成所需的初始化工作。
类和对象的变量(原书中是类和对象的方法,应是笔误)
我们已经讨论了类与对象的功能部分,现在我们来看一下它的数据部分。事实上,它们只是与 类和对象的名称空间 绑定 的普通变量,即这些名称只在这些类与对象的前提下有效。
有两种类型的 域 ——类的变量和对象的变量,它们根据是类还是对象 拥有 这个变量而区分。
类的变量 由一个类的所有对象(实例)共享使用。只有一个类变量的拷贝,所以当某个对象 对类的变量做了改动的时候,这个改动会反映到所有其他的实例上。
对象的变量 由类的每个对象或实例拥有。因此每个对象有自己对这个域的一份拷贝,即它们不 是共享的,在同一个类的不同实例中,虽然对象的变量有相同的名称,但是是互不相关的。通 过一个例子会使这个易于理解。
使用类和对象的变量
例11.4
#!/usr/bin/python
#Filename:objvar.py
class person:
'''Represents a person'''
population=0
def __init__(self,name):
'''Initializes the person's data'''
self.name=name
print '(Initializing %s)'%self.name
person.population+=1
def __del__(self):
'''I am dying'''
print '%s says bye.'%self.name
person.population-=1
if person.population==0:
print 'I am the last one.'
else:
print 'There are still %d people left.'%person.population
def sayHi(self):
'''Greeting by the person.
Really,that's all it does.'''
print 'Hi,my name is %s.'%self.name
def howMany(self):
'''Prints the current population.'''
if person.population==1:
print 'I am the only person here.'
else:
print 'We have %d persons here.'%person.population
swaroop=person('Swaroop')
swaroop.sayHi()
swaroop.howMany()
kalam=person('Abdul Kalam')
kalam.sayHi()
kalam.howMany()
swaroop.sayHi()
swaroop.howMany()
输出:
# python objvar.py
(Initializing Swaroop)
Hi,my name is Swaroop.
I am the only person here.
(Initializing Abdul Kalam)
Hi,my name is Abdul Kalam.
We have 2 persons here.
Hi,my name is Swaroop.
We have 2 persons here.
Abdul Kalam says bye.
There are still 1 people left.
Swaroop says bye.
I am the last one.
如何工作:
这是一个很长的例子,但是它有助于说明类与对象的变量的本质。这里,population属于Person 类,因此是一个类的变量。name变量属于对象(它使用self赋值)因此是对象的变量。
观察可以发现__init__方法用一个名字来初始化Person实例。在这个方法中,我们让population 增加1,这是因为我们增加了一个人。同样可以发现,self.name的值根据每个对象指定,这表 明了它作为对象的变量的本质。
记住,你只能使用self变量来参考同一个对象的变量和方法。这被称为 属性参考 。
在这个程序中,我们还看到docstring对于类和方法同样有用。我们可以在运行时使用Person. __doc__和Person.sayHi.__doc__来分别访问类与方法的文档字符串。
就如同__init__方法一样,还有一个特殊的方法__del__,它在对象消逝的时候被调用。对象消 逝即对象不再被使用,它所占用的内存将返回给系统作它用。在这个方法里面,我们只是简单 地把Person.population减1。
当对象不再被使用时,__del__方法运行,但是很难保证这个方法究竟在 什么时候 运行。如果 你想要指明它的运行,你就得使用del语句,就如同我们在以前的例子中使用的那样。
注:对比方法调用和输出可以看出__del__的执行同样不需要专门调用。在对象消逝或者说进程的生命结束时__del__就自动运行了。
继承:
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过 继承 机 制。继承完全可以理解成类之间的 类型和子类型 关系。
假设你想要写一个程序来记录学校之中的教师和学生情况。他们有一些共同属性,比如姓名、 年龄和地址。他们也有专有的属性,比如教师的薪水、课程和假期,学生的成绩和学费。
你可以为教师和学生建立两个独立的类来处理它们,但是这样做的话,如果要增加一个新的共 有属性,就意味着要在这两个独立的类中都增加这个属性。这很快就会显得不实用。
一个比较好的方法是创建一个共同的类称为SchoolMember然后让教师和学生的类 继承 这个共 同的类。即它们都是这个类型(类)的子类型,然后我们再为这些子类型添加专有的属性。
使用这种方法有很多优点。如果我们增加或改变了SchoolMember中的任何功能,它会自动地反 映到子类型之中。例如,你要为教师和学生都增加一个新的身份证域,那么你只需简单地把它 加到SchoolMember类中。然而,在一个子类型之中做的改动不会影响到别的子类型。另外一个 优点是你可以把教师和学生对象都作为SchoolMember对象来使用,这在某些场合特别有用,比 如统计学校成员的人数。一个子类型在任何需要父类型的场合可以被替换成父类型,即对象可 以被视作是父类的实例,这种现象被称为多态现象。
另外,我们会发现在 重用 父类的代码的时候,我们无需在不同的类中重复它。而如果我们使 用独立的类的话,我们就不得不这么做了。
在上述的场合中,SchoolMember类被称为 基本类 或 超类 。而Teacher和Student类被称为 导出 类 或 子类 。
使用继承
例11.5
#!/usr/bin/python
#Filename:inherit.py
class SchoolMember:
'''Represents any school member.'''
def __init__(self,name,age):
self.name=name
self.age=age
print '(Initialized SchoolMember:%s)'%self.name
def tell(self):
'''Tell my details.'''
print 'Name:"%s" Age:"%s"'%(self.name,self.age)
class Teacher(SchoolMember):
'''Represents a teacher.'''
def __init__(self,name,age,salary):
SchoolMember.__init__(self,name,age)
self.salary=salary
print '(Initialized Teacher:%s)'%self.name
def tell(self):
SchoolMember.tell(self)
print 'Salary:"%d"'%self.salary
class Student(SchoolMember):
'''Represents a student.'''
def __init__(self,name,age,marks):
SchoolMember.__init__(self,name,age)
self.marks=marks
print '(Initialized Student:%s)'%self.name
def tell(self):
SchoolMember.tell(self)
print 'Marks:"%d"'%self.marks
t=Teacher('Mrs.Shrividya',40,30000)
s=Student('Swaroop',22,75)
print
schmembers=[t,s]
for schmember in schmembers:
schmember.tell()
输出:
# python inherit.py
(Initialized SchoolMember:Mrs.Shrividya)
(Initialized Teacher:Mrs.Shrividya)
(Initialized SchoolMember:Swaroop)
(Initialized Student:Swaroop)
Name:"Mrs.Shrividya" Age:"40"
Salary:"30000"
Name:"Swaroop" Age:"22"
Marks:"75"
如何工作:
为了使用继承,我们把基本类的名称作为一个元组跟在定义类时的类名称之后。然后,我们注 意到基本类的__init__方法专门使用self变量调用,这样我们就可以初始化对象的基本类部分。 这一点十分重要——Python不会自动调用基本类的constructor,你得亲自专门调用它。
我们还观察到我们在方法调用之前加上类名称前缀,然后把self变量及其他参数传递给它。
注意,在我们使用SchoolMember类的tell方法的时候,我们把Teacher和Student的实例仅仅作为 SchoolMember的实例。
另外,在这个例子中,我们调用了子类型的tell方法,而不是SchoolMember类的tell方法。可以 这样来理解,Python总是首先查找对应类型的方法,在这个例子中就是如此。如果它不能在导 出类中找到对应的方法,它才开始到基本类中逐个查找。基本类是在类定义的时候,在元组之 中指明的。
一个术语的注释——如果在继承元组中列了一个以上的类,那么它就被称作 多重继承 。
注:在程序源码的最后三行中,可以看出Python程序到底有多接近人类语言——
方法一: members = [t, s]
for member in members:
member.tell() # works for both Teachers and Students
方法二: schmembers=[t,s]
for schmember in schmembers:
schmember.tell()
刚开始我以为members作为一个数组,member代表数组元素一定是系统特定的格式。结果我自创了另外一个单词schmember,结果可以同理使用。Python这方面真的很夸张,非常非常像自然语言。
概括:
我们已经研究了类和对象的多个内容以及与它们相关的多个术语。通过本章,你已经了解了面 向对象的编程的优点和缺陷。Python是一个高度面向对象的语言,理解这些概念会在将来有助 于你进一步深入学习Python。
接下来,我们将学习如何处理输入/输出已经如何用Python访问文件。
12输入/输出
在很多时候,你会想要让你的程序与用户(可能是你自己)交互。你会从用户那里得到输入, 然后打印一些结果。我们可以分别使用raw_input和print语句来完成这些功能。对于输出,你也 可以使用多种多样的str(字符串)类。例如,你能够使用rjust方法来得到一个按一定宽度右对 齐的字符串。利用help(str)获得更多详情。
另一个常用的输入/输出类型是处理文件。创建、读和写文件的能力是许多程序所必需的,我 们将会在这章探索如何实现这些功能。
文件
你可以通过创建一个file类的对象来打开一个文件,分别使用file类的read、readline或write方法来 恰当地读写文件。对文件的读写能力依赖于你在打开文件时指定的模式。最后,当你完成对文 件的操作的时候,你调用close方法来告诉Python我们完成了对文件的使用
使用文件
例12.1
#!/usr/bin/python
#Filename:using_file.py
poem='''\
Programming is fun
When the work is done
if you wanna make you work also fun:
use Python!
'''
f=file('poem.txt','w')
f.write(poem)
f.close()
f=file('poem.txt')
while True:
line=f.readline()
if len(line)==0:
break
print line,
f.close()
输出:
# python using_file.py
Programming is fun
When the work is done
if you wanna make you work also fun:
use Python!
如何工作:
首先,我们通过指明我们希望打开的文件和模式来创建一个file类的实例。模式可以为读模式 ('r')、写模式('w')或追加模式('a')。事实上还有多得多的模式可以使用,你可以使用 help(file)来了解它们的详情。
我们首先用写模式打开文件,然后使用file类的write方法来写文件,最后我们用close关闭这个文 件。
接下来,我们再一次打开同一个文件来读文件。如果我们没有指定模式,读模式会作为默认的 模式。在一个循环中,我们使用readline方法读文件的每一行。这个方法返回包括行末换行符 的一个完整行。所以,当一个 空的 字符串被返回的时候,即表示文件末已经到达了,于是我 们停止循环。
注意,因为从文件读到的内容已经以换行符结尾,所以我们在print语句上使用逗号来消除自动 换行。最后,我们用close关闭这个文件。
现在,来看一下poem.txt文件的内容来验证程序确实工作正常了。
# cat poem.txt
Programming is fun
When the work is done
if you wanna make you work also fun:
use Python!
笔记:
这里我认为最需要理解的是语句f=file('poem.txt','w')
还记得之前说过的Python几乎把一切都当做对象来处理吗。这里file是文件类,f是对象,是file类的实例,poem.txt用来给f的一个域赋值。而之后f.write(),f.close()都是类的方法。
储存器
Python提供一个标准的模块,称为pickle。使用它你可以在一个文件中储存任何Python对象,之 后你又可以把它完整无缺地取出来。这被称为 持久地 储存对象。
还有另一个模块称为cPickle,它的功能和pickle模块完全相同,只不过它是用C语言编写的,因 此要快得多(比pickle快1000倍)。你可以使用它们中的任一个,而我们在这里将使用cPickle模 块。记住,我们把这两个模块都简称为pickle模块。
储存和取储存
例12.2
#!/usr/bin/python
#Filename:pickling.py
import cPickle as p
shoplistfile='shoplist.data'
shoplist=['apple','mango','carrot']
f=file(shoplistfile,'w')
p.dump(shoplist,f)
f.close()
del shoplist
f=file(shoplistfile)
storedlist=p.load(f)
f.close()
print storedlist
输出:
# python pickling.py
['apple', 'mango', 'carrot']
如何工作:
首先,请注意我们使用了import..as语法。这是一种便利方法,以便于我们可以使用更短的模块 名称。在这个例子中,它还让我们能够通过简单地改变一行就切换到另一个模块(cPickle或者 pickle)!在程序的其余部分的时候,我们简单地把这个模块称为p。
为了在文件里储存一个对象,首先以写模式打开一个file对象,然后调用储存器模块的dump函 数,把对象储存到打开的文件中。这个过程称为 储存 。
接下来,我们使用pickle模块的load函数的返回来取回对象。这个过程称为 取储存 。
笔记:两个知识点
1.import..as懒人专用,引用模块后立即给模块一个简称,用起来省力气。
2.pickle模块的两个函数p.dump (储存)p.load(取储存)。敲一遍就知道怎么回事了,不难。
概括:
我们已经讨论了多种类型的输入/输出,及文件处理和使用储存器模块。
接下来,我们将探索异常的概念。
刚刚写完两章的内容我就发现这已经和上两次的博客篇幅一样多了。后面的这几个章节例题源码比较长,而且内容比较多也更难理解。因此我决定这篇博客到此打住。最后四章的内容下次我会一并更新。
其实我已经看完了全书,只是整理并做好笔记发出来也是一件比较消耗精力的事。在更新完《简明Python教程》读书笔记之后会开始编写爬虫的实战部分。其实爬虫这玩意儿我不清楚应该怎么着手,感觉需要几天整理思路。真希望身边有个Python大神能够带带路(抱紧大腿)。