三. Python的面向对象编程
3.1 Python的类的基础
3.1.1 创建一个类
#!/usr/bin/python
# Filename: simplestclass.py
class Person:
pass # An empty block
p = Person() # 创建类对象
print(p)
输出
$ python simplestclass.py
<__main__.Person instance at 0xf6fcb18c>
我们使用class语句后跟类名,创建了一个新的类。这后面跟着一个缩进的语句块形成类体。在这个例子中,我们使用了一个空白块,它由pass语句表示。
接下来,我们使用类名后跟一对圆括号来创建一个对象/实例。(我们将在下面的章节中学习更多的如何创建实例的方法)。为了验证,我们简单地打印了这个变量的类型。它告诉我们我们已经在__main__模块中有了一个Person类的实例。可以注意到存储对象的计算机内存地址也打印了出来。
3.1.2 self 相当于C++的this指针
在类方法的定义时, 需要在第一个参数位置添加self, 而在调用时不需要为该参数赋值, Python自动为我们添加类对象的指针给self.(这和C++的是一样的)
这个特别的变量指对象本身,按照惯例它的名称是self。
3.1.3 使用类对象的方法
例11.2 使用对象的方法
#!/usr/bin/python
# Filename: method.py
class Person:
def sayHi(self): # 定义类方法, 注意这个self
print 'Hello, how are you?'
p = Person()
p.sayHi()
# This short example can also be written as Person().sayHi()
(源文件:code/method.py)
输出
$ python method.py
Hello, how are you?
这里我们看到了self的用法。注意sayHi方法没有任何参数,但仍然在函数定义时有self。
3.1.4 __init__方法(相当于C++中构造函数)
__init__方法在类的一个对象被建立时,马上运行, 相当于C++中构造函数。你可以在里面做一些初始化工作 。注意,这个名称的开始和结尾都是双下划线。
使用__init__方法
例11.3 使用__init__方法
#!/usr/bin/python
# Filename: class_init.py
class Person:
def __init__(self, name): # 类方法定义时, 第一个参数都是self
self.name = name # 初始化工作
# 注意name不需要在类中声明, 直接赋值就代表声明了
def sayHi(self):
print('Hello, my name is', self.name)
p = Person('Swaroop') # 使用时不需要填入self
p.sayHi()
# This short example can also be written as Person('Swaroop').sayHi()
输出
$ python class_init.py
Hello, my name is Swaroop
我们把__init__方法定义为取一个参数name(以及普通的参数self)。
3.1.5 类与对象的方法
类与对象的变量只是与类和对象的名称空间 绑定 的普通变量,即这些名称只在这些类与对象的前提下有效。
有两种类型的域 ——类的变量和对象的变量,它们根据是类还是对象拥有这个变量而区分。
类的变量相当于C++类的静态变量. 对象的变量相当于C++类的非静态变量.
使用类与对象的变量
例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 # self.name 就是对象的变量了
print('(Initializing %s)' % self.name)
# When this person is created, he/she
# adds to the population
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.
Python中所有的类成员(包括数据成员)都是 公共的 ,所有的方法都是 有效的 。
只有一个例外:如果你使用的数据成员名称以 双下划线前缀 比如__privatevar,Python的名称管理体系会有效地把它作为私有变量。
这样就有一个惯例,如果某个变量只想在类或对象中使用,就应该以单下划线前缀。而其他的名称都将作为公共的,可以被其他类/对象使用。记住这只是一个惯例,并不是Python所要求的(与双下划线前缀不同)。
3.1.6 继承
例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的
self.salary = salary
print('(Initialized Teacher: %s)' % self.name)
def tell(self): # 重载了父类的tell方法
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的
self.marks = marks
print('(Initialized Student: %s)' % self.name)
def tell(self): # 重载了父类的tell方法
SchoolMember.tell(self)
print('Marks: "%d"' % self.marks)
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 22, 75)
print() # prints a blank line
members = [t, s]
for member in members: # 循环调用tell
member.tell() # works for both Teachers and Students
输出
$ 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传递给父类的__init__函数的
我们还观察到我们在方法调用之前加上类名称前缀,然后把self变量及其他参数传递给它。
注意,在我们使用SchoolMember类的tell方法的时候,我们把Teacher和Student的实例仅仅作为SchoolMember的实例。
Python总是首先查找子类的方法, 如果它不能在导出类中找到对应的方法,它才开始到基本类中逐个查找。基本类是在类定义的时候,在元组之中指明的。
3.2. Python的输出输入
3.2.1 Python读写文件
你可以通过创建一个file类的对象来打开一个文件,分别使用file类的read、readline或write方法来恰当地读写文件。对文件的读写能力依赖于你在打开文件时指定的模式。最后,当你完成对文件的操作的时候,你调用close方法来告诉Python我们完成了对文件的使用。
#!/usr/bin/python
# Filename: using_file.py
poem = '''\
Programming is fun
When the work is done
if you wanna make your work also fun:
use Python!
'''
f = open('poem.txt', 'w') # open for 'w'riting 打开文件写方式
f.write(poem) # write text to file写入文本到文件
f.close() # close the file关闭文件
f = open('poem.txt')#再次打开文件
# if no mode is specified, 'r'ead mode is assumed by default
while True:
line = f.readline() # 循环读取文件, 知道读取到EOF文件尾
if len(line) == 0: # Zero length indicates EOF
break
print(line)
# Notice comma to avoid automatic newline added by Python
f.close() # close the file
3.2.2 储存器(用户Python对象的序列化/持久化)
Python提供一个标准的模块,称为pickle。使用它你可以在一个文件中储存任何Python对象,之后你又可以把它完整无缺地取出来。这被称为 持久地 储存对象。
还有另一个模块称为cPickle,它的功能和pickle模块完全相同,只不过它是用C语言编写的,因此要快得多(比pickle快1000倍)。你可以使用它们中的任一个,而我们在这里将使用cPickle模块。记住,我们把这两个模块都简称为pickle模块。
#!/usr/bin/python
# Filename: pickling.py
#import cPickle as p 命名空间使用as来重命名
import pickle as p
shoplistfile = 'shoplist.data'
# the name of the file where we will store the object
shoplist = ['apple', 'mango', 'carrot']
# Write to the file 写入Python对象到文件
f = open(shoplistfile, 'wb')
p.dump(shoplist, f) # dump the object to a file
f.close()
del shoplist # remove the shoplist
# Read back from the storage 重新读取
f = open(shoplistfile, 'rb')
storedlist = p.load(f)
print(storedlist)
输出
$ python pickling.py
['apple', 'mango', 'carrot']
3.3 异常处理
3.3.1用 try..finally 捕捉异常
#!/usr/bin/python
# Filename: try_except.py
import sys
try:
s = input('Enter something --> ')
except EOFError: # 这里指定捕捉指定类型的异常
print('\nWhy did you do an EOF on me?')
sys.exit() # exit the program
except: # 捕捉所有类型的异常.
print('\nSome error/exception occurred.')
# here, we are not exiting the program
print('Done')
我们把所有可能引发错误的语句放在try块中,然后在except从句/块中处理所有的错误和异常。except从句可以专门处理单一的错误或异常,或者一组包括在圆括号内的错误/异常。如果没有给出错误或异常的名称,它会处理 所有的 错误和异常。对于每个try从句,至少都有一个相关联的except从句。
如果某个错误或异常没有被处理,默认的Python处理器就会被调用。它会终止程序的运行,并且打印一个消息,我们已经看到了这样的处理。你还可以让try..catch块关联上一个else从句。当没有异常发生的时候,else从句将被执行。我们还可以得到异常对象,从而获取更多有个这个异常的信息。
3.3.2 抛出异常
你可以使用raise语句 引发 异常。你还得指明错误/异常的名称和伴随异常 触发的 异常对象。你可以引发的错误或异常应该分别是一个Error或Exception类的直接或间接导出类。
#!/usr/bin/python
# Filename: raising.py
# 定义一个异常子类
class ShortInputException(Exception):
'''A user-defined exception class.'''
def __init__(self, length, atleast):
Exception.__init__(self)
self.length = length
self.atleast = atleast
try:
s = input('Enter something --> ')
if len(s) < 3:
raise ShortInputException(len(s), 3) # 如果输入长度小于3, 使用 raise 抛出一个异常
# Other work can continue as usual here
except EOFError:
print('\nWhy did you do an EOF on me?')
except ShortInputException as x: # 注意x 代表异常类对象的名称
print('ShortInputException: The input was of length %d, \
was expecting at least %d' % (x.length, x.atleast))
else:
print('No exception was raised.')
3.3.3 try..finally
在一个try块下,你可以同时使用except从句和finally块。如果你要同时使用它们的话,需要把一个嵌入另外一个。
#!/usr/bin/python
# Filename: finally.py
import time
try:
f = open('poem.txt')
while True: # our usual file-reading idiom
line = f.readline()
if len(line) == 0:
break
time.sleep(2)
print(line)
finally:
f.close()
print('Cleaning up...closed the file')
输出
$ python finally.py
Programming is fun
When the work is done
Cleaning up...closed the file
Traceback (most recent call last):
File "finally.py", line 12, in ?
time.sleep(2)
KeyboardInterrupt
我们进行通常的读文件工作,但是我有意在每打印一行之前用time.sleep方法暂停2秒钟。这样做的原因是让程序运行得慢一些(Python由于其本质通常运行得很快)。在程序运行的时候,按Ctrl-c中断/取消程序。
我们可以观察到KeyboardInterrupt异常被触发,程序退出。但是在程序退出之前,finally从句仍然被执行,把文件关闭.
也就是说finally块总是会执行的, 无论异常有没有发生.
3.4 Python的标准库
3.4.1 sys模块
sys模块包含系统对应的功能。
例14.1 使用sys.argv
#!/usr/bin/python
# Filename: cat.py
import sys
def readfile(filename):
'''Print a file to the standard output.'''
f = open(filename)
while True:
line = f.readline()
if len(line) == 0:
break
print(line) # notice comma
f.close()
# Script starts from here
if len(sys.argv) < 2:
print('No action specified.')
sys.exit()
if sys.argv[1].startswith('--'):
option = sys.argv[1][2:]
# fetch sys.argv[1] but without the first two characters
if option == 'version':
print('Version 1.2')
elif option == 'help':
print('''\
This program prints files to the standard output.
Any number of files can be specified.
Options include:
--version : Prints the version number
--help : Display this help''')
else:
print('Unknown option.')
sys.exit()
else:
for filename in sys.argv[1:]:
readfile(filename)
在Python程序运行的时候,即不是在交互模式下,在sys.argv列表中总是至少有一个项目。它就是当前运行的程序名称,作为sys.argv[0](由于Python从0开始计数)。其他的命令行参数在这个项目之后。
为了使这个程序对用户更加友好,我们提供了一些用户可以指定的选项来了解更多程序的内容。我们使用第一个参数来检验我们的程序是否被指定了选项。如果使用了--version选项,程序的版本号将被打印出来。类似地,如果指定了--help选项,我们提供一些关于程序的解释。我们使用sys.exit函数退出正在运行的程序。和以往一样,你可以看一下help(sys.exit)来了解更多详情。
如果没有指定任何选项,而是为程序提供文件名的话,它就简单地打印出每个文件地每一行,按照命令行中的顺序一个文件接着一个文件地打印。
顺便说一下,名称cat是 concatenate 的缩写,它基本上表明了程序的功能——它可以在输出打印一个文件或者把两个或两个以上文件连接/级连在一起打印。
sys.version字符串给你提供安装的Python的版本信息。
sys.version_info元组则提供一个更简单的方法来使你的程序具备Python版本要求功能。
[swaroop@localhost code]$ python
>>> import sys
>>> sys.version
'2.3.4 (#1, Oct 26 2004, 16:42:40) \n[GCC 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)]'
>>> sys.version_info
(2, 3, 4, 'final', 0)
对于有经验的程序员,sys模块中其他令人感兴趣的项目有sys.stdin、sys.stdout和sys.stderr它们分别对应你的程序的标准输入、标准输出和标准错误流。
3.4.2 os模块
这个模块包含普遍的操作系统功能。如果你希望你的程序能够与平台无关的话,这个模块是尤为重要的。即它允许一个程序在编写后不需要任何改动,也不会发生任何问题,就可以在Linux和Windows下运行。一个例子就是使用os.sep可以取代操作系统特定的路径分割符。
下面列出了一些在os模块中比较有用的部分。它们中的大多数都简单明了。
os.name字符串指示你正在使用的平台。比如对于Windows,它是'nt',而对于Linux/Unix用户,它是'posix'。
os.getcwd()函数得到当前工作目录,即当前Python脚本工作的目录路径。
os.getenv()和os.putenv()函数分别用来读取和设置环境变量。
os.listdir()返回指定目录下的所有文件和目录名。
os.remove()函数用来删除一个文件。
os.system()函数用来运行shell命令。
os.linesep字符串给出当前平台使用的行终止符。例如,Windows使用'\r\n',Linux使用'\n'而Mac使用'\r'。
os.path.split()函数返回一个路径的目录名和文件名。
>>> os.path.split('/home/swaroop/byte/code/poem.txt')
('/home/swaroop/byte/code', 'poem.txt')
os.path.isfile()和os.path.isdir()函数分别检验给出的路径是一个文件还是目录。类似地,os.path.existe()函数用来检验给出的路径是否真地存在。
你可以利用Python标准文档去探索更多有关这些函数和变量的详细知识。你也可以使用help(sys)等等。
3.5 Python其他的一些知识
3.5.1 一些特殊的方法
特殊的方法(我的理解就是框架方法了, 设定好了框架, 你在上面填充好就可以了)
在类中有一些特殊的方法具有特殊的意义,比如__init__和__del__方法,它们的重要性我们已经学习过了。
一般说来,特殊的方法都被用来模仿某个行为。例如,如果你想要为你的类使用x[key]这样的索引操作(就像列表和元组一样),那么你只需要实现__getitem__()方法就可以了。想一下,Python就是对list类这样做的!
下面这个表中列出了一些有用的特殊方法。如果你想要知道所有的特殊方法,你可以在《Python参考手册》中找到一个庞大的列表。
表15.1 一些特殊的方法
名称 说明
__init__(self,...) 这个方法在新建对象恰好要被返回使用之前被调用。 (构造函数)
__del__(self) 恰好在对象要被删除之前调用。 (析构函数)
__str__(self) 在我们对对象使用print语句或是使用str()的时候调用。
__lt__(self,other) 当使用 小于 运算符(<)的时候调用。类似地,对于所有的运算符(+,>等等)都有特殊的方法。
__getitem__(self,key) 使用x[key]索引操作符的时候调用。 相当于操作符[]
__len__(self) 对序列对象使用内建的len()函数的时候调用。
3.5.2 单语句
每一个语句块是通过它的缩进层次与其它块区分开来的(相当于C++中的花括号)。但是, 如果你的语句块只包含一句语句,那么你可以在条件语句或循环语句的同一行指明它。(此时不需要通过缩进也可以分开代码块)
>>> flag = True
>>> if flag: print 'Yes'
...
Yes
但这可能是一种不好的习惯, 尽量少用.
3.5.3 列表综合
通过列表综合,可以从一个已有的列表导出一个新的列表。
例如,你有一个数的列表,而你想要得到一个对应的列表,使其中所有大于2的数都是原来的2倍。
对于这种应用,列表综合是最理想的方法。
例15.1 使用列表综合
#!/usr/bin/python
# Filename: list_comprehension.py
listone = [2, 3, 4]
listtwo = [2 * i for i in listone if i > 2] # 注意这里的格式
print(listtwo)
输出
$ python list_comprehension.py
[6, 8]
它如何工作
这里我们为满足条件(if i > 2)的数指定了一个操作(2*i),从而导出一个新的列表。注意原来的列表并没有发生变化。在很多时候,我们都是使用循环来处理列表中的每一个元素,而使用列表综合可以用一种更加精确、简洁、清楚的方法完成相同的工作。
3.5.4 在函数中接收元组和列表
当要使函数接收元组或字典形式的参数的时候,有一种特殊的方法,它分别使用*和**前缀。这种方法在函数需要获取可变数量的参数的时候特别有用。
>>> def powersum(power, *args):
... '''Return the sum of each argument raised to specified power.'''
... total = 0
... for i in args:
... total += pow(i, power)
... return total
...
>>> powersum(2, 3, 4)
25
>>> powersum(2, 10)
100
由于在args变量前有*前缀,所有多余的函数参数都会作为一个元组存储在args中。如果使用的是**前缀,多余的参数则会被认为是一个字典的键/值对。
3.5.5 lambda形式(这个不是很明白)
lambda语句被用来创建新的函数对象,并且在运行时返回它们。
例15.2 使用lambda形式
#!/usr/bin/python
# Filename: lambda.py
def make_repeater(n):
return lambda s: s*n # lambda
twice = make_repeater(2) # 设置 n是2
print(twice('word')) # word是lambda的s
print(twice(5))
输出
$ python lambda.py
wordword
10
它如何工作
这里,我们使用了make_repeater函数在运行时创建新的函数对象,并且返回它。lambda语句用来创建函数对象。本质上,lambda需要一个参数,后面仅跟单个表达式作为函数体,而表达式的值被这个新建的函数返回。注意,即便是print语句也不能用在lambda形式中,只能使用表达式。
3.5.6 exec和eval语句(这个强大啊)
exec语句用来执行储存在字符串或文件中的Python语句。例如,我们可以在运行时生成一个包含Python代码的字符串,然后使用exec语句执行这些语句。下面是一个简单的例子。
>>> exec('print "Hello World"')
Hello World
eval语句用来计算存储在字符串中的有效Python表达式。
下面是一个简单的例子。
>>> eval('2*3')
6
这两个语句强大, 相当于字符串也可以作为命令执行, 在C++中必须自己写代码实现的, 而对于解析性的语言, 这是很简单的事情.
现在我的理解结合 lambda来试一下:
#!/usr/bin/python
# Filename: lambda1.py
def make_repeater(n):
return lambda s: s*n
twice = make_repeater(2)
print(twice(exec('print("word")')))
print(twice(5))
会发生异常
3.5.7 assert语句
(C++中也有这个东西, 调试版时有效, 就是用来验证用的)
assert语句用来声明某个条件是真的。例如,如果你非常确信某个你使用的列表中至少有一个元素,而你想要检验这一点,并且在它非真的时候引发一个错误,那么assert语句是应用在这种情形下的理想语句。当assert语句失败的时候,会引发一个AssertionError。
>>> mylist = ['item']
>>> assert len(mylist) >= 1
>>> mylist.pop()
'item'
>>> assert len(mylist) >= 1
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AssertionError
3.5.8 repr函数
repr函数用来取得对象的规范字符串表示。反引号(也称转换符,在键盘左上角与~同一个键)可以完成相同的功能。注意,在大多数时候有eval(repr(object)) == object。
>>> i = []
>>> i.append('item')
>>> `i`
"['item']"
>>> repr(i)
"['item']"
基本上,repr函数和反引号用来获取对象的可打印的表示形式。你可以通过定义类的__repr__方法来控制你的对象在被repr函数调用的时候返回的内容。