Python学习笔记七 面向对象编程
参考教程:廖雪峰官网https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
面向对象编程
面向对象编程——Object Oriented Programming ,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
通过一个实例来对比面向过程和面向对象程序的区别。
例如要处理学生的成绩,按面向过程的方式,则需要先定义学生的成绩,再通过一个函数把信息打印出来:
std1 = { 'name': 'Michael', 'score': 98 } std2 = { 'name': 'Bob', 'score': 81 } def print_score(std): print('%s: %s' % (std['name'], std['score'])) print_score(std1) print_score(std2)
如果采用面向对象的程序设计思想,思考的侧重点不是程序的执行流程。首先对于学生的分数、姓名的处理可以通过设计一种数据类型去实现,这种数据类型拥有姓名、分数这两个属性,其实这就对应了对象和属性的特性,并且我们可以给这个对象设计其特有的函数(方法)。
class Student(object): def __init__(self,name,score): self.name=name self.score=score def print_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类创建一个输出其属性的方法:
def print_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类定义如下:
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score))
修改后就无法访问实例对象的__name和__score属性了。
如果外部需要访问这两个属性,那可以增加get_name和get_score方法:
def get_name(self): return self.__name def get_score(self): return self.__score
如果更进一步还可以让外部通过一些特定的方法可以修改属性值,那么就需要给类增加set方法:
def set_name(self, name): self.__name = name def set_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 -*- class Student(object): def __init__(self, name, gender): self.name = name self.__gender = gender def get_gender(self): return self.__gender def set_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类:
class Animal(object): def run(self): print('Animal is running...')
我们可以编写其他的类作为这个Animal类的子类,只需要在定义类时将参数设为'Animal':
class Dog(Animal): pass class Cat(Animal): pass Dog().run() mycat=Cat() mycat.run()
这样Dog和Cat类也自动拥有了run()这个方法。这里Cat和Dog类就是Animal的子类,而Animal就是它们的父类。
但我们也可以发现,在Dog和Cat的实例对象调用run()时候,输出的内容仍然是父类定义的'Animal is running...',这里我们也可以给Dog类和Cat类设置同名的run()方法:
class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...') Dog().run() mycat=Cat() mycat.run()
这样输出就变成了:
Dog is running... Cat is 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))
而对于子类创建的实例对象,其数据类型既是子类,也可以是父类。这就是多态的一种理解。
要充分理解多态的好处,可以看下面这个例子:
def run_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... ''' #如果新增一个类 class Sheep(Animal): pass run_twice(Sheep()) '''输出: Animal is running... Animal is running... ''' #我们可以发现并不需要修改run_twice方法,就可以引用sheep的实例 #这是因为run_twice方法接收的是Animal类型的数据,而sheep是其子类 #更进一步的实质是:Animal类型具备run方法,所以其子类都有这个方法 #所以实质上在动态语言中,对于run_twice这样的方法只需要参数具备run方法即可,如下实例 class X(object): def run(self): print('X is running') run_twice(X()) '''输出: X is running X is running '''
四、实例属性和类属性
class Student(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__()方法中给类设置属性外,也可以在类的定义中直接设置:
class Student(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删除 del henry.score print(henry.score) #输出98,此时又继承了类的属性
练习:
''' 为了统计学生人数,可以给Student类增加一个类属性 每创建一个实例,该属性自动增加 ''' class Student(object): count=0 def __init__(self,name): self.__name=name self.set_count() def set_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('测试通过!')