而解决这一问题的比较有效的方法之一就是数据隐藏,即编码过程中尽可能的隐藏内部的实现细节。
面向对象的程序设计是数据隐藏的一个非常有效的思路,那什么是面向对象呢?
面向对象的核心思想是将一些共性的对象抽象成一个类,然后再由类来构造新的对象。面向对象思想符合我们认识问题从特殊到一般,再从一般到特殊的这样一个过程。
比如,我们在程序中构造学生这样一个类,包含姓名、性别、年龄、年级、家庭住址等属性,那我们在程序中需要表征某个具体的学生的时候,我们就可以使用刚刚构造的学生这个类来创建一个学生这样的对象,然后在创建这个学生对象的时候学生类会自动的要求我们给这个学生赋予姓名、性别、年龄、年级、家庭住址等属性,我们创建的每一个学生对象都具备这些属性,非常统一、也减少了出错的几率。
类除了属性之外,还可以封装一些方法,这些方法呢代表这个类的一些操作。还是以学生为例,我们可以定义给学生的某门功课评分这样一个方法,以后老师想要给某个学生打分,直接调用这个学生对象的对应的方法就可以实现了。
我们将数据进行抽象封装以后,可以避免外部的干扰;另外,类还可以继承,比如我们在学生的这个类的继承上还可以构建一个毕业生这样的子类,可以很方便地扩展代码;有了面向对象的思想,我们可以较方便地将现实世界中的问题映射到程序中,减少软件开发中的中间环节的转换。
先看一段代码
class Student:
def setName(self,name):
self.name = name
def setGender(self,gender):
self.gender = gender
def getName(self):
return self.name
def getGender(self):
return self.gender
上面这段代码,定义了一个Student类,有setName\getName两个方法,我们可以像下面这样来使用它。
>>> stu1 = Student()
>>> stu1.setName('Gao')
>>> print stu1.getName()
Gao
在Python中,创建一个对象跟调用函数差不多,用类名作为关键字创建了一个stu1的Student类对象。这里需要注意的是,我们定义的时候,类的方法都有个参数self,但是调用的时候都没有写这个self,这个是默认自动传入的,代指这个对象自身。
上一小节,我们虽然定义了一个类,但是在创建这个类的对象的时候好像还有个问题。就是个学生在创建的时候,他(她)的姓名、性别应该一次性都给他确定下来,这是他本身就有的属性,如果每个属性还需要调用一下set方法来设置,那就太啰嗦了。
所以Python有个__init__方法让我们把上面的代码优化一下。
class Pokemon:
def __init__(self, name, gender, level, type, status):
self.type = type
self.gender = gender
self.name = name
self.level = level
self.status = [10 + 2*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level]
#最大HP,攻击,防御,特攻,特防,速度
def getName(self):
return self.name
def getGender(self):
return self.gender
def getType(self):
return self.type
def getStatus(self):
return self.status
上面的代码相对于前一段进行了更改,加了个__init__方法。这个方法虽然也是手动添加的,但它的地位非常特殊,它是构造方法。在创建对象的之后呢,我们的Python解释器会自动的调用这个构造函数,将一个类中必须的一些属性给赋值好,不至于说漏掉什么东西。比如下面这样使用:
p1 = Charmander('Ding','male')
Traceback (most recent call last):
File "<ipython-input-4-1bad90e35e89>", line 1, in <module>
p1 = Charmander('Ding','male')
TypeError: __init__() takes exactly 4 arguments (3 given)
这里试图使用Charmander类来创建一个p1的对象,但是因为在调用构造函数的时候输入的参数不对,所以就报错。改成这样就没错了:
p1 = Charmander('Ding','male',5)
细心的朋友可能已经发现,前面我们错误提示说__init__方法需要输入4个参数,但是我们只输入了3个参数就可以通过了,那是因为它的第一个参数"self",是隐含参数在这里代指要创建的这个对象p1自身,会自动传入。
上面的代码还有一点点问题,就是init构造方法构造的类的几个属性它们不是私有的,这样在外面可以通过p1.name这样来访问或更改,造成数据不安全。和我们面向对象封装的初衷相违背了,我们要想办法把这些属性给私有化,在python这步操作比较简单,只需要这样就可以了。
class Pokemon:
def __init__(self, name, gender, level, type, status):
self.__type = type
self.__gender = gender
self.__name = name
self.__level = level
self.__status = [10 + 2*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level]
当然类自身的方法在访问这几个属性的时候也要加上双下划线,经过这样改造后的属性,我们在外部就没法直接通过p1.name这样的形式来访问了,就必须通过类定义的接口,对属性的更改也需要用类自定义的接口。比如:
def level_up(self):
self.__status = [s+1 for s in self.__status]
self.__status[0] += 1 #HP每增加一级加2点,其它加1点
这个接口level_up(self)就允许通过对象在外部调用,然后更改私有的属性值。
当类的属性比较多的时候,我们希望能通过迭代器非常方便地依次将它们输出,这也是有办法的。我们需要将我们的类在上面的基础上进一步升级。
class Charmander:
def __init__(self, name, gender, level, type, status):
self.__type = type
self.__gender = gender
self.__name = name
self.__level = level
self.__status = status
#最大HP,攻击,防御,特攻,特防,速度
self.__info = [self.__name, self.__type, self.__gender,
self.__level, self.__status]
self.__index = -1
def getName(self):
return self.__name
def getGender(self):
return self.__gender
def getType(self):
return self.__type
def getStatus(self):
return self.__status
def level_up(self):
self.__status = [s+1 for s in self.__status]
self.__status[0] += 1 #HP每增加一级加2点,其它加1点
def __iter__(self):
print '名字 属性 性别 等级 能力'
return self
def next(self):
if self.__index == len(self.__info)-1:
raise StopIteration
self.__index += 1
return self.__info[self.__index]
上面代码中,我们做了3处更改。(1)在__init_构造方法中,加入了__info属性,这是一个list,list里面呢就是我们待会希望迭代输出的一些属性;增加了__index属性,这个值初始化为-1,待会在迭代的时候用来标识前面那个list的角标。(2)增加了一个__iter__(self)方法,这个方法呢就是为后面生成迭代器使用的,需要配合next函数使用,而且必须返回一个self(就是被迭代的那个对象自身)。(3)增加了一个next(self)方法,这个方法有两个作用,第一是当迭代器指向超出最后一个元素的时候抛出一个异常,第二当迭代器指向没错误的时候返回迭代的list中的元素。
具体用法如下:
p1 = Charmander('Ding','male',5)
for i in p1:
print i
名字 属性 性别 等级 能力
Ding
('fire', None)
male
5
[20, 10, 10, 10, 10, 10]
type([1,23])
Out[10]: list
我们使用for循环可以很方便的对p1的属性进行迭代。
我们在程序中引入函数、类这些东东的主要目的就是为了减少复杂工程中的重复劳动,让我们的庞大的软件工程更清晰。
在类里有个非常优秀的机制,叫做继承。继承呢,和我们生物学上父生子的概念不太一样,倒有点像种、属、科、目这样的。就好比说,老虎、猫都属于猫科动物,我们就可以定义两个子类老虎、猫,然后再定义一个父类“猫科”,老虎、猫这两个子类呢继承了父类的一些东西。
我们对上面的代码继续升级:
class Pokemon:
def __init__(self, name, gender, level, type, status):
self.__type = type
self.__gender = gender
self.__name = name
self.__level = level
self.__status = status
#最大HP,攻击,防御,特攻,特防,速度
self.__info = status
self.__index = -1
def getName(self):
return self.__name
def getGender(self):
return self.__gender
def getType(self):
return self.__type
def getStatus(self):
return self.__status
def level_up(self):
self.__status = [s+1 for s in self.__status]
self.__status[0] += 1 #HP每增加一级加2点,其它加1点
def __iter__(self):
print '名字 属性 性别 等级 能力'
return self
def next(self):
if self.__index == len(self.__info)-1:
raise StopIteration
self.__index += 1
return self.__info[self.__index]
class Charmander(Pokemon):
def __init__(self, name, gender, level):
self.__type = ('fire', None)
self.__gender = gender
self.__name = name
self.__level = level
self.__status = [10+2*level, 5+1*level,
5+1*level, 5+1*level, 5+1*level, 5+1*level ]
Pokemon.__init__(self, self.__name, self.__gender,
self.__level,self.__type, self.__status)
上面,有两个类Charmander、Pokemon。Charmander类后面还加了括弧,里面写着Pokemon。这样呢,表示Pokemon是父类(基类),Charmander是子类,Charmander的方法都是继承自父类Pokemon,自己就只是把自己的一些特别的属性给初始化一下就可以了,不需要做太多事情了,老子都帮它做好了,多省事。
在使用的时候,如下:
p2 = Charmander('Bang','female',3)
p2.level_up()
p2.getStatus()
Out[13]: [18, 9, 9, 9, 9, 9]
上面代码,p2是直接用Charmander创建的对象,照样可以使用Pokemon中的方法。
Python面向对象的概念不难理解,真正比较难的是我们在程序设计中使用面向对象的思想对我们遇到的实际软件问题进行抽象,而这个抽象呢需要我们从面向过程的C语言式的软件思维中跳出来。
《Python与数据挖掘》张良均,等著,,机械工业出版社
https://www.runoob.com/python/python-object.html