python面向对象编程72讲_Python学习笔记七 面向对象编程

面向对象编程

面向对象编程——Object Oriented Programming ,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

通过一个实例来对比面向过程和面向对象程序的区别。

例如要处理学生的成绩,按面向过程的方式,则需要先定义学生的成绩,再通过一个函数把信息打印出来:

std1 = { 'name': 'Michael', 'score': 98}

std2= { 'name': 'Bob', 'score': 81}defprint_score(std):print('%s: %s' % (std['name'], std['score']))

print_score(std1)

print_score(std2)

如果采用面向对象的程序设计思想,思考的侧重点不是程序的执行流程。首先对于学生的分数、姓名的处理可以通过设计一种数据类型去实现,这种数据类型拥有姓名、分数这两个属性,其实这就对应了对象和属性的特性,并且我们可以给这个对象设计其特有的函数(方法)。

classStudent(object):def __init__(self,name,score):

self.name=name

self.score=scoredefprint_score(self):print('%s: %s' % (self.name, self.score))

bart = Student('Bart Simpson', 59)

lisa = Student('Lisa Simpson', 87)

bart.print_score()

lisa.print_score()

对于上例,Student就是一个类(class),而bart和lisa就是这个类的两个实例(instance)对象。

一、类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),比如上例中的Student就是一个类,而其中bart和lisa就是依据这个类创建出来的具体对象,即实例。

类名通常用大写字母开头表示,参数表示这个类是从哪个类继承下来的,如果没有合适的继承类,就使用object类,object类是所有类都会继承的类。类的实例通过“类名()"创建。

类的定义中可以通过__init__方法给类创建其属性,如:

#第一个参数表示创建的实例本身

def __init__(self,name,score):

self.name=name

self.score=score

而在创建实例的时候,不需要传递第一个self参数,Python解释器会自行把实例变量传进去:

bar=Student('Bart Simpson',60)

通过创建上面的实例对象bart之后,就可以用bart.name/bart.score去访问该实例的属性。

而对于类来说,属性的访问,包括一些常用的操作都可以在类定义代码中统一实现,这也称为类的方法。如给上面的Student类创建一个输出其属性的方法:

defprint_score(self):print('%s: %s' % (self.name, self.score))

在实例对象调用该方法的时候也不需要传递self参数:

bart.print_score()

需要注意的是,和静态语言不同,Python允许对实例对象绑定任何数据:

#创建两个Student类的实例

bart=Student('Bart',60)

lisa=Student('Lisa',80)#给bart绑定age属性

bart.age=12

#输出12

print(bart.age)#报错,提示Student类没有age属性

print(lisa.age)

二、访问限制

为保证属性不被外部访问,可以在类的定义中对于需要保护的属性名前面加上'__',这样这个属性就变成了私有变量(private),只有内部可以访问。

修改后的Student类定义如下:

classStudent(object):def __init__(self, name, score):

self.__name =name

self.__score =scoredefprint_score(self):print('%s: %s' % (self.__name, self.__score))

修改后就无法访问实例对象的__name和__score属性了。

如果外部需要访问这两个属性,那可以增加get_name和get_score方法:

defget_name(self):return self.__name

defget_score(self):return self.__score

如果更进一步还可以让外部通过一些特定的方法可以修改属性值,那么就需要给类增加set方法:

defset_name(self, name):

self.__name =namedefset_score(self, score):

self.__score = score

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量.

另外对于上例中的私有变量__name和__score要记得它们实质上的名称是_Student__name和_Student__score;所以在外部对实例对象设置__name是可行的,但只是给该实例增加了一个名为__name的属性。而通过类方法get_name()可以发现实际属性值并没有变化。

>>> bart = Student('Bart Simpson', 59)>>>bart.get_name()'Bart Simpson'

>>> bart.__name = 'New Name' #设置__name变量!

>>> bart.__name

'New Name'

>>> bart.get_name() #get_name()内部返回self.__name

'Bart Simpson

练习:

'''请把下面的Student对象的gender字段对外隐藏起来

用get_gender()和set_gender()代替,并检查参数有效性:'''

#-*- coding: utf-8 -*-

classStudent(object):def __init__(self, name, gender):

self.name=name

self.__gender =genderdefget_gender(self):return self.__gender

defset_gender(self,gender):

self.__gender=gender#测试:

bart = Student('Bart', 'male')if bart.get_gender() != 'male':print('测试失败!')else:

bart.set_gender('female')if bart.get_gender() != 'female':print('测试失败!')else:print('测试成功!')

三、继承和多态

在OOP程序设计中,在定义一个类的时候,可以在参数中指定这个类所继承的已存在的类,比如之前的Student继承了object类。新定义的类称为子类(Subclass),而被继承的类称为基类、父类或超类(Base class、Super class)。

比如已有如下的一个Animal类:

classAnimal(object):defrun(self):print('Animal is running...')

我们可以编写其他的类作为这个Animal类的子类,只需要在定义类时将参数设为'Animal':

classDog(Animal):pass

classCat(Animal):passDog().run()

mycat=Cat()

mycat.run()

这样Dog和Cat类也自动拥有了run()这个方法。这里Cat和Dog类就是Animal的子类,而Animal就是它们的父类。

但我们也可以发现,在Dog和Cat的实例对象调用run()时候,输出的内容仍然是父类定义的'Animal is running...',这里我们也可以给Dog类和Cat类设置同名的run()方法:

classDog(Animal):defrun(self):print('Dog is running...')classCat(Animal):defrun(self):print('Cat is running...')

Dog().run()

mycat=Cat()

mycat.run()

这样输出就变成了:

Dog isrunning...

Catis running...

当子类和父类拥有同名方法时候,在子类中的方法就“覆盖”了父类的方法,在子类的实例对象调用这个方法时也就直接使用了子类定义的方法。

当定义了一个类的时候,其实在当前的Python运行环境里就是多了一种数据类型,这可以通过isinstance()判断:

print(isinstance(10,int))print(isinstance('abc',str))print(isinstance(Dog(),Animal))print(isinstance(Cat(),Animal))print(isinstance(Dog(),Dog))print(isinstance(Cat(),Cat))'''输出:

True

True

True

True

True

True'''

但要注意下面这种情况:

#对于通过父类创建的实例对象#其数据类型是不属于该父类的子类的

#False

print(isinstance(Animal(),Dog))

而对于子类创建的实例对象,其数据类型既是子类,也可以是父类。这就是多态的一种理解。

要充分理解多态的好处,可以看下面这个例子:

defrun_twice(animal):

animal.run()

animal.run()#当传入Animal类的实例时:

run_twice(Animal())'''输出:

Animal is running...

Animal is running...'''

#当传入Dog类或Cat类的实例时:

run_twice(Dog())'''输出:

Dog is running...

Dog is running...'''

#如果新增一个类

classSheep(Animal):passrun_twice(Sheep())'''输出:

Animal is running...

Animal is running...'''

#我们可以发现并不需要修改run_twice方法,就可以引用sheep的实例#这是因为run_twice方法接收的是Animal类型的数据,而sheep是其子类#更进一步的实质是:Animal类型具备run方法,所以其子类都有这个方法#所以实质上在动态语言中,对于run_twice这样的方法只需要参数具备run方法即可,如下实例

classX(object):defrun(self):print('X is running')

run_twice(X())'''输出:

X is running

X is running'''

四、实例属性和类属性

classStudent(object):def __init__(self,name):

self.name=name

s=Student('bob')#输出'bob'

print(s.name)

s.score=100

#输出100

print(s.score)#报错:Student类没有score属性

print(Student('Aly').score)

除了在类的__init__()方法中给类设置属性外,也可以在类的定义中直接设置:

classStudent(object):def __init__(self,name):

self.name=name

score=98

#可以直接通过类名访问score属性

print(Student.score)#也可以通过类的实例访问

print(Student('Bob').score)

此时如果给类的实例对象赋予一个同名的属性,则会覆盖掉类的属性:

anny=Student('Anny')

anny.score=100

print(anny.score) #输出100,此时类属性score被覆盖

henry=Student('Henry')print(henry.score) #输出98,此时为继承类的属性

#给henry增加score属性

henry.score=99

print(henry.score) #输出99,此时类属性score被覆盖

#实例中增加的属性可以用del删除

delhenry.scoreprint(henry.score) #输出98,此时又继承了类的属性

练习:

'''为了统计学生人数,可以给Student类增加一个类属性

每创建一个实例,该属性自动增加'''

classStudent(object):

count=0def __init__(self,name):

self.__name=name

self.set_count()defset_count(self):

Student.count=Student.count+1

#测试:

if Student.count !=0:print('测试失败!')else:

bart= Student('Bart')if Student.count != 1:print('测试失败!')else:

lisa= Student('Bart')if Student.count != 2:print('测试失败!')else:print('Students:', Student.count)print('测试通过!')