Python面向对象编程
Posted 礁之
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python面向对象编程相关的知识,希望对你有一定的参考价值。
文章目录
此文参考廖雪峰官网:面向对象编程 - 廖雪峰的官方网站 (liaoxuefeng.com)
一、什么是面向对象编程
面向对象编程(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
,其实是调用的是另外一个变量
- 在Python中,变量名称类似于
五、特性之二、三——继承和多态
-继承
-
在上面的内容中,有说到过
继承
这一概念,例如: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
以上是关于Python面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章
Python面向对象:面向对象(OOP)基本概念类和对象基础语法封装案例私有属性和私有方法