一、什么是面向对象编程
面向对象编程(Object Oriented Programming),简称OOP,是一种程序设计思想,OOP把对象当作程序的基本单元,一个对象包含了数据和操作数据的函数
面向对象的程序设计把计算机程序当作一组对象的集合,每个对象都可以接受其他对象发来的消息,并且进行处理,而计算机程序的执行就是一系列消息在各对象之间进行传递,和面向对象不同,面向过程的程序设计把计算机程序当作一系列的命令集合,即一组函数的顺序执行,为了简化程序设计,面向过程把函数继续切分为子函数,从而降低系统的复杂度
在Python中,所有的数据类型都可以看作为对象,当然也可以自定义对象,自定义的对象数据类型就是面向对象中类(class)的概念,下面来看一个案例来说明面向对象和面向过程的区别:
- 现在我们需要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以使用一个字典表示,例如: # -*- coding: utf-8 -*- std_1 = {'name':'zhangsan','score':98} std_2 = {'name':'lisi','score':97} def print_score(std): return '%s : %s' % (std['name'],std['score']) print(print_score(std_1)) print(print_score(std_2)) #输出: zhangsan : 98 lisi : 97 - 如果使用面向对象的程序设计思想,首先思考的不是程序的执行流程,而是'学生'的这种数据类型应该被看作一个'对象',这个对象有'name'和'score'两种'属性(property)',如果想要输出一个学生的成绩,首先就需要先创建一个'学生'对应的对象,然后给这个'学生'对象发送一个'打印成绩'的'消息',让对象自己把指定的数据打印出来,例如: # -*- coding: utf-8 -*- 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)) - 给对象发送消息实际就是调用对象对应的'关联函数',这个关联函数也叫做'对象的方法',下面就是面向对象的程序调用 zhangsan = Student('zhangsan',98) lisi = Student('lisi',97) zhangsan.print_score() lisi.print_score() #输出: zhangsan : 98 lisi : 97
对象(class)是一种抽象的概念,上面定义的对象Student,指的就是学生这个概念,而实例(instance)则指一个个具体的对象,例如上面的zhangsan和lisi就是两个具体的Student,也就是实例
从上面的案例可以看出,面向对象的程序设计思想其实就是抽象出对象(class),然后根据对象创建实例(instance)
最后,面向对象的抽象程度比函数高,因为一个对象既包含数据,也包含操作数据的方法,数据封装、继承、多态是面向对象的三大特点
二、类(class)和实例(instance)
- 面对对象最重要的概念就是
类(class)
和实例(instance)
,类是抽象的模板,比如上面的Student类
,而实例是根据类创建出来的具体的对象,每个对象都有相同的方法,但是各自的数据可能不同,例如上面的zhangsan
和lisi
- 以
Student
类为例:
# -*- coding: utf-8 -*- class Student(object): pass
(1)先看第2行
class Student(object): 在Python中,类是通过'class'关键字进行定义的,'class'后面跟着的是类名,类名通常是以大写字母开头的,紧接着就是'(object)',这个表示的是'Student'类是从'object'类继承下来的,继承这个概念在后面会说 通常如果说没有合适的继承类,那么就可以直接使用'object'类,这是所有的类最终都会继承的类
(2)定义好了Student类,就可以根据Student类创建出Student的实例,而创建实例是通过类名()实现的,例如:
>>> class Student(object): ... pass ... >>> zhangsan = Student() >>> zhangsan <__main__.Student object at 0x0000018C3E6A6A10> >>> Student <class '__main__.Student'> 可以看到,变量'zhangsan'指向的是'Student'实例,输出的信息中,'object at'后面是内存地址,每个object的地址都不一样,而'Student'本身就是一个类 还可以给一个实例变量自由的绑定属性,例如: >>> zhangsan.name = 'zhangsan' >>> zhangsan.name 'zhangsan'
(3)由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些必要的属性写进入,通过一个特殊的__init__方法,在创建实例的时候,把name和score等属性绑定,例如:
>>> class Student(object): ... def __init__(self,name,score): ... self.name = name ... self.score = score ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.name 'zhangsan' >>> zhangsan.score 98 >>> lisi = Student() #传入空参数 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Student.__init__() missing 2 required positional arguments: 'name' and 'score' >>> lisi = Student('lisi',97,22) #多传入一个参数 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Student.__init__() takes 3 positional arguments but 4 were given #注意特殊方法__init__是两个_ 可以看到在定义特殊方法'__init__'时,后面的第一个参数是'self',而且也必须是'self',这个参数表示创建的实例本身,因此,在'__init__'方法内部,就可以把各种属性绑定到'self',因为'self'就指向创建的实例本身 不过,在有了'__init__'方法之后,在创建实例的时候,就不能传入空参数或多个参数,必须传入和'__init__'方法相匹配的参数,但是'self'参数不需要传,Python解释器会自己把'实例变量'传入到self
(4)和普通函数相比,在类中定义的函数只有一点不同,那就是函数的第一个参数永远都是self,并且在调用该函数时,不用传递参数,除此之外,和普通函数没有其他区别,仍然可以使用默认参数、可变参数、关键字参数、命名关键字参数
三、特性之一——数据封装
面向对面编程的一个重要的特点就是数据封装,在上面的Student类中,根据对象创建的实例里,都有各自的name和score的数据,可以通过函数来访问这些数据,例如:
>>> class Student(object): ... def __init__(self,name,score): ... self.name = name ... self.score = score ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.name 'zhangsan' >>> zhangsan.score 98 >>> def print_score(std): ... return '%s : %s' % (std.name,std.score) ... >>> print_score(zhangsan) 'zhangsan : 98'
但是,既然Student
实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student
类的内部定义访问数据的函数,这样,就可以把数据
封装起来了,这些封装数据的函数和Student
类本身是关联起来的,我们称之为类的方法,更改后可以这样写:
>>> class Student(object): ... def __init__(self,name,score): ... self.name = name ... self.score = score ... def print_score(self): ... return '%s : %s' % (self.name,self.score) ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.print_score() 'zhangsan : 98' 可以发现,'zhangsan'可以直接引用'Student'类中的'print_score'函数 同样的'print_score'函数的参数也是'self',也是不用传递的,直接在实例变量上调用即可 如果有第三个参数age,但是在__init__中并没有定义,可以这样写: # -*- coding: utf-8 -*- class Student(object): def __init__(self,name,score): self.name = name self.score = score def print_score(self): return print('%s %s %s' % (self.name,self.score,self.age)) zhangsan = Student('zhangsan',98) zhangsan.age = 22 #定义实例变量age zhangsan.print_score() #输出: zhangsan 98 22
- 从上面可以看出,在根据
Student
类创建实例时,只需要指定name
和score
的值即可,关于如何打印出来,这些都是在Student
类的内部定义的,从而使这些数据和逻辑被封装
起来,调用时并不知道内部的细节 - 封装的另一个好处就是可以给
Student
类增加新的方法,例如:
# -*- coding: utf-8 -*- class Student(object): def __init__(self,name,score): self.name = name self.score = score def print_score(self): return print('%s : %s' % (self.name,self.score)) def get_grade(self): if self.score >= 90 and self.score <= 100: return print('A') elif self.score >= 80: return print('B') else: return print('C') zhangsan = Student('zhangsan',98) zhangsan.print_score() zhangsan.get_grade() #输出: zhangsan : 98 A
总结:
类是创建实例的模板,而实例是一个个具体的对象,每个实例拥有的数据都是相互独立的,互不影响
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据
通过在实例上调用方法,其实就是直接操作了对象内部的数据,并且无需指定方法内部的实现细节
和静态语音不同,Python允许对实例变量绑定任何数据,这样的效果就是,就算是根据同一类创建出的实例,实例拥有的变量名称可能都是不一样的,例如:
# -*- coding: utf-8 -*- class Student(object): def __init__(self,name,score): self.name = name self.score = score zhangsan = Student('zhangsan',98) lisi = Student('lisi',98) zhangsan.age = 22 lisi.aaa = 333 print(zhangsan.age) print(lisi.aaa) print(zhangsan.aaa) #输出 22 333 Traceback (most recent call last): File "d:\工作\work\py\test02.py", line 15, in <module> print(zhangsan.aaa) AttributeError: 'Student' object has no attribute 'aaa' 因为可以绑定任何数据,所有说'zhangsan'拥有name,score,age三个变量,而'lisi'则拥有name,score,aaa三个变量,因为是实例是相互独立的,所有'zhangsan'和'lisi'之间的变量数量、变量名等都不是互通的,所以zhangsan在最后调用aaa变量时报错了
四、访问限制
在类(class)中,可以有属性和方法,而外部代码可以通过调用实例变量的方法来操作或获取数据,从而隐藏了内部的复杂逻辑
但是,从上面Student类的定义来看,外部代码可以随意改变一个实例的属性,例如:
>>> class Student(object): ... def __init__(self,name,score): ... self.name = name ... self.score = score ... def print_score(self): ... return print('%s : %s' % (self.name,self.score)) ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.print_score() zhangsan : 98 >>> zhangsan.score = 22 #修改score的值 >>> zhangsan.print_score() #再次调用,发现值已经变了 zhangsan : 22
可以看到实例属性的值是可以随意修改的,如果想要实例的内部属性不被外部修改,可以这样做:
#可以在属性的名称前面加"__",在Python中,实例的变量名如果以__开头的话,那么这个变量就成了私有变量,只有内部可以访问,外部无法访问 >>> class Student(object): ... def __init__(self,name,score): ... self.__name = name ... self.__score = score ... def print_score(self): ... return print('%s : %s' % (self.__name,self.__score)) ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.print_score() zhangsan : 98 #现在想对Student类中的score属性的值进行修改,再调用print_score方法时,发现并没有修改,最后发现,其实这相当于是新创建了一个score属性 >>> zhangsan.score = 22 >>> zhangsan.print_score() zhangsan : 98 >>> zhangsan.score 22 #上面不行的话,有的人可能会说是因为属性名称不一样,那么现在来调用一下__score属性,发现也无法调用,这是因为在使用私有变量后,Python解释器就把__name的对外名称变成了_Student__score,使用这样的格式进行调用,是可以调用成功的,不过强烈建议不要使用这种方法进行修改、调用属性数据 >>> zhangsan.__score Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__score'. Did you mean: 'score'? >>> zhangsan._Student__score 98 #在不使用_Student__score这种格式去访问、修改指定的属性时,可以修改一下类,例如 >>> class Student(object): ... def __init__(self,name,score): ... self.__name = name ... self.__score = score ... def print_score(self): ... return print('%s : %s' % (self.__name,self.__score)) ... def set_name(self,name): ... self.__name = name ... def set_score(self,score): ... self.__score = score ... def get_name(self): ... return print(self.__name) ... def get_score(self): ... return print(self.__score) ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.print_score() zhangsan : 98 >>> zhangsan.get_name() zhangsan >>> zhangsan.set_name('lisi') >>> zhangsan.get_name() lisi #虽然原先的"zhangsan.name = 98"也可以之间进行修改,但是通过在类中添加方法可以进行参数控制,例如: >>> class Student(object): ... def __init__(self,name,score): ... self.__name = name ... self.__score = score ... def set_score(self,score): ... if score >= 90 and score <=100: ... self.__score = score ... else: ... return print('error') ... def get_score(self): ... return self.__score ... >>> zhangsan = Student('zhangsan',98) >>> zhangsan.get_score() 98 >>> zhangsan.set_score(80) error >>> zhangsan.get_score() 98 >>> zhangsan.set_score(96) >>> zhangsan.get_score() 96 可以看到在'Student'类中,'set_score'方法添加了参数控制
注意:
在Python中,变量名称类似于__xx__这样的以双下划线开头和结尾的是特殊变量,特殊变量是可以直接访问的,并不是私有变量
以一个下划线开头的,类似于_name这样的变量,是可以被外部访问的,但是在看到这样的变量时,要把它当成私有变量,不要随意访问,这也是一个不成文的规定
根据拥有私有变量的类去创建的实例,在调用时,不要直接使用类似于zhangsan.__score 这样的,因为虽然在类中定义的是__score,但其实Python解释器对外的名称是_Student__score,所以直接调用或修改zhangsan.__score,其实是调用的是另外一个变量
五、特性之二、三——继承和多态
-继承
- 在上面的内容中,有说到过
继承
这一概念,例如:
class Student(object)
在这里Student(object)的object就是Student继承的类,也就是说,我们在定义类时,是可以继承现有的类的,创建的新类叫做子类(subclass),而被继承的类叫做父类,也可以叫做基类、超类
下面来看几个案例:
- 定义一个'Animal'类,添加一个'run'方法 >>> class Animal(object): ... def run(self): ... return print('Animal is running!!!') ... - 定义'Dog'和'Cat'类,两个类都继承'Animal'类 >>> class Dog(Animal): ... pass ... >>> class Cat(Animal): ... pass ... - 创建'Dog'和'Cat'的实例,发现创建后的实例是可以直接使用'Animal'的方法的 >>> dog = Dog() >>> dog.run() Animal is running!!! >>> cat = Cat() >>> cat.run() Animal is running!!!
- 对于上面的案例来说,
Dog
和Cat
就是Animal
的子类,Animal
是Dog
和Cat
的父类 - 可以看到在
Dog
和Cat
继承Animal
后,是可以直接使用Animal
中的方法的,这也是继承最大的好处:子类可以获得父类的全部功能 - 当然,也可以对子类修改或增加一些方法,例如:
>>> class Dog(Animal): ... def run(self): ... return print('Dog is running!!!') ... def eat(self): ... return print('Dog is Eating meat') ... - 需要注意的是,在修改完'Dog'类之后,直接调用'dog.run'变量,值还是原来的,需要重新创建实例 >>> dog.run() Animal is running!!! >>> dog = Dog() >>> dog.run() Dog is running!!! >>> dog.eat() Dog is Eating meat
在对子类添加、修改方法后,可以看到创建实例并调用时,输出的run变量的值变成了子类Dog中的run方法的输出,而不是Animal类中的run方法的输出,这是因为:当子类和父类同时存在相同的方法时,在调用子类的时候,子类的方法会覆盖掉父类的方法。
例如:Dog是Animal的子类,并且Dog和Animal类中都有run的方法,在这种情况下调用Dog的实例dog.run(),输出的是Dog类中run方法的输出,而不是Animal的