之前我们讲了Python面向过程的一面,现在我们来说说它面向对象的一面,学完这章你就会发现,写Python原来就是天天搞对象,就连过程都是它的对象!那什么是面向对象的编程呢,我们可以和面向过程对比一下。
举个例子,我想吃奥利奥。从过程角度看,我就要经过扭一扭、舔一舔、泡一泡、吃下去这几个步骤才能完成吃奥利奥这个事情。从对象角度看(太详细写会占篇幅,这里会省略一些),这件事参与者有手、奥利奥、舌头、牛奶、嘴,那么首先我要得知这几者的具体信息:手——有五个指头,可以扭动、奥利奥——一个带馅儿的由两个饼干构成的夹心饼干......然后就是具体的做法了,用两只手的五指将夹心的奥利奥的两片饼干扭动后,将两片饼干分开,馅儿会分别粘在两个饼干上,然后......
从这个例子我们可以发现几个事情。“扭一扭”等这些过程就好比一个个封装好的函数,一个函数实现一个过程。但是可以看到,无论哪个过程都会出现奥利奥这个东西,所以每个过程的关联度很高,也即耦合性很强。从对象的角度,每个角色都在做自己的动作,有自己的特点,比如手做扭的动作,而奥利奥刚好是一个有两个饼干的夹心,他们是完全分开的角色,所以面向对象的耦合性很弱。我们再看看面向对象里具体做法的描述,乍一看这不就是“扭一扭”的过程嘛。不过在看过广告之前,一说“扭一扭”这个词,大家肯定不知道是什么意思,因为这是对某一件事的高度抽象概括而来的几个过程,只有仔细阅读这个抽象的具体内容,也就是看过广告的,才知道“扭一扭”为何。而这个抽象的具体内容,是由一个个对象演绎而成的,所以说通过对象的描述,虽然复杂很多,但能够实在的还原事物原本的过程。
所以综上,面向对象的程序语言是更加贴近人的语言,而面向过程更贴近于机器。在如今计算机性能大幅提高的今天,我们越来越重视语言向人的方向发展,利用面向对象语言,程序员可以更快更好的开发出新的产品,大大提高的应用产能的发展。在对面向对象有一点了解后,我们来学学Python面向对象的语法。
1 给对象归类
对象满地球都是,什么《高等数学》、猫、老师......这么杂,我们首先要归个类吧,生物界还分七等呢,如果不分就没有层次,事物看起来会很乱。比如天上这么多飞来飞去的,我们暂且归为鸟类吧。那鸟都有什么呢?比如怎么运动、吃什么、怎么吃?我们来写写看:
class Bird():
'''
这是个鸟类
'''
move = 'fly'
food = 'meat'
def eat(self, name, thing):
print(name,' eat a ',thing,' !')
class
是“类”的关键字,Bird是我取的类的名字。里面就是鸟类的属性了,我们还是用三个单引号表示帮助文档,像move这类属性称为成员变量。利用def
关键字,我们可以定义方法,也就是这个类的一些动作,这里指鸟吃东西的动作。这里的方法其实和函数差不多,括号里面是参数。唯一不同就是作为类的方法,括号内必须有self
关键字,是指“自己”,也就是在方法里可以调用自己类的成员变量和方法,具体调用后面讲。
其实Python有许多内置方法,这些方法名字都用双下划线括起,我们这里讲一个常用的方法,名为__init__()
。这个方法的作用是,在你利用这个类创建一个对象时,它会立刻执行,比如我们现在改写一下鸟类:
class Bird(object):
move = 'fly'
food = 'meat'
def __init__(self, name):
self.name = name
def eat(self, thing):
print(self.name,' eat a ',thing,' !')
这个类现在新增加了init方法,每创建一次对象,这个方法便会执行。创建方法在后面讲解。现在我们已经创建了一个比较完整的鸟类,但这个类过于宽泛,鸟类名下有很多鸟,每个鸟有自己独特的属性,比如羽毛颜色会不一样,这该怎么办?Python中,类可以继承,继承的类包含父类中所有的属性,也会执行父类的内置方法,而且我们可以重写父类,更改父类的方法。比如我们现在创建天鹅类,天鹅也是鸟的一种呀。
class Swan(Bird):
feather_color = 'white' # 新的成员变量
def eat(self): # 重写eat方法
print('I ate a frog !')
天鹅类,它继承了鸟类的属性外,我还添加了羽毛颜色,并且重写了吃的方法。添加的属性会在这一子类生效,而重写的方法会代替父类的方法生效。我们还可以看到,在定义鸟类时,括号写的是object
,现在写的是Bird,可知括号内写的是继承的类,object
是对象类,是所有类的总和,也即是一个根类,一般第一个定义的就用object继承,而后面我们继承鸟类,所以括号写Bird。
2 我想要一个对象
女娲有了人的模型后捏了一堆的人从而创造了人类。我们现在“捏”了这么多类,也该有自己的对象了。有了鸟类,现在就创建一只鸟吧,我们就叫他spring。
spring = Bird('spring')
看,只需要一行代码,你就有一个对象了。原本只需要写Bird()
即可,但我们类里有一个init
方法,这个方法里有一个name
参数,我们需要在这里写入,执行完该语句后,程序会立刻执行init
方法。在方法里,我们使用了self.name
,.
是用于引用类属性的,点前写类的名字,后写要调用的成员或方法即可。这里self
是自己,也就是调用成员变量name
并进行初始化。也即在执行完这行代码后,我们就有了name
成员变量了。现在我们试试调用里面的属性和方法:
print(spring.name, spring.move, spring.food) # 调用成员变量
# 输出: springflymeat
spring.eat('worm') # 调用成员方法
# 输出:spring eat a worm !
我们再造一只天鹅试试:
summer = Swam('summer') # 创建对象
print(summer.name, summer.food, summer.feather_color) # 调用成员变量
# 输出:summermeatwhit
summer.eat # 调用重写方法
# 输出:I ate a frog !
我们看到天鹅确实继承了鸟类的属性,同时也按照重写的eat
方法输出。所以说,我们想要一个对象,要先有它的类,再通过类创造对象。我们可以调用对象的方法实现许多事情。感兴趣的可以试试把吃奥利奥的事情写写。
3 一切皆对象
虽然说Python集成了面向过程、面向对象等于一身的超级语言,但究其本身,就是面向对象,一切的衍生都是利用对象写出的。先说说之前学到的几种数据结构,我曾提到它有很多的函数可以用,其实这些函数恰巧是处理这些数据的方法。Python把这些数据结构看做对象,通过对象方法进行处理。我们加下来详细说说处理他们的方法。
3.1 列表
list.count(5) # 查看列表有几个元素5
list.index(3) # 查询第一个出现3的元素下标
list.append(6) # 在列表最后一个添加元素6
list.sort() # 排序
list.reverse() # 反转次序
list.pop() # 删除最后一个元素并返回该元素
list. remove(2) # 删除第一个元素2
list. insert(0, 9) # 在第0位置插入元素2
list. clear() # 清空列表
3.2 元组和字符串
tuple.count(5) # 查看元组有几个元素5
tuple.index(3) # 查询元组第一个出现3的元素下标
str = 'hello world!'
sub = 'world'
str.count(sub) # 返回sub在str出现的次数
str. find(sub) # 从左开始,返回找到第一个sub的第一个字符的位置,没有返回-1
str. index(sub) # 从左开始,返回找到的第一个sub的索引,没有则报错
str. rfind(sub) # 从右开始,返回找到第一个sub的第一个字符的位置,没有返回-1
str. rindex(sub) # 从右开始,返回找到的第一个sub的索引,没有则报错
str. isalnum() # 查看是否所有字符都是字母或数字,是则返回True,否则False
str. isalpha() # 查看是否所有字符都是字母,是则返回True,否则False
str. isdigit() # 查看是否所有字符都是数字,是则返回True,否则False
str. istitle() # 查看是否所有字符都是首字母大写,是则返回True,否则False
str. isspace() # 查看是否所有字符都是空格,是则返回True,否则False
str. islower() # 查看是否所有字符都是,都是小写,是则返回True,否则False
str. isupper() # 查看是否所有字符都是大写,是则返回True,否则False
str. splite([sep, [max]]) # 以sep为分隔符分隔字符串max次,默认用空格,都为选填
str. rsplite([sep, [max]]) # 从右开始分隔
str. join(s) # 返回以str分隔的s
str. strip([cub]) # 去掉字符串去掉开头结尾的sub,默认为空格
str. replace(sub, new_sub) # 用new_sub代替字符串中的sub
str. capitalize() # 返回开头字母大写的字符串
str. lower() # 返回所有字母小写的字符串
str. upper() # 返回所有字母大写的字符串
str. swapcase() # 返回大小写全部反转的字符串
str. title() # 将开头字母全部大写(以空格分开)
str. center(width) # 在width长度内将字符串居中
str. ljust(width) # 在width长度内将字符串居右
str. rjust(width) # 在width长度内将字符串居左
3.3 词典
dict.values() # 返回词典所有值(可用于循环遍历)
dict.keys() # 返回词典所有键(可用于循环遍历)
dict.clear() # 清空词典
不光是这些数据结构,就连面向过程的一些方法,都是用面向对象实现的。
3.4 循环
每一个for
循环都会自动寻找下一个变量的元素,事实上,它是通过迭代器和迭代器中的__next__()
方法实现循环的,只是for循环就是运用这个原理,可以省略这样的写法。当next方法后已经是末尾元素,系统会返回StopIteration异常告知循环结束。我们可以尝试利用面向对象的方法直接实现循环:
list = [1, 2]
iterator = iter(list) # iter是迭代器转换函数,可以将变量转换成迭代器
iterator.next()
iterator.__next__()
iterator.__next__() # 报错
如果我们想从头再次开始,则需要重新定义一个迭代器。上面的写法其实可直接用for循环实现:
for i in [1, 2]:
循环对象对于处理庞大数据的循环是有好处的,它可以边遍历,边导入变量的元素,这样可以让计算机少占用内存。其实我们可以借助生成器实现自定义循环对象,可以一次性循环多种不同的元素。生成器是一个函数,用def
关键字定义,内部每完成一种对象的定义就可以用yield
关键字截断一次,程序会对这个元素先做一次循环遍历,再找下一个。举个例子:
def gen():
a = 100
yield a
a = a*8
yield a
yield 1000
for i in gen():
print(i)
可以看到,for
循环首先截断在第一个a的元素并做一次循环,并用相同的方法进行后两次循环相当于将数据分成3组循环,在数据量庞大的情况下,可以节省内存。
3.5 函数
函数也是一个对象,事实上它是利用创建类里的__call__()
方法实现的,我们可以看看:
class function(object):
def __call__(self, a)
return a+5
其实这种写法完全等价于:
def function(a):
return a+5
只是后者本质上还是调用前者的方法实现的,之后我们只需用正常的方法调用:
print(function(3)) # 输出8
所以说Python的函数其实是使用面向对象的方法实现的。
3.6 模块和异常
我们之前讲到许多调用函数的方法,包括自己定义的,以及Python库。事实上他们也可以看做对象,所以这些库又可以称为模块,我们是利用import
和from
这两个方法调用这些模块。那么同理,异常也是对象,所以异常对象其实也有一些方法,下面举例:
try:
a = 1/0
except :
ZeroDivisionError as e:
print('catch ZeroDivisionError')
print(e.message)
这里首先我们利用as
关键字,将ZeroDivisionError
这个错误名称用e代替,事实上这个在其他地方也可以用。我们调用e
对象的message
方法,查看异常信息。
具体实例代码可以看我的码云:第四章样例代码