面向对象编程
一、对象的概念
面向对象是一门编程思想
核心是“对象”二字
对象指的是“特征与技能”的结合体
- 优点
- 可扩展性高
- 缺点
- 编写程序的复杂程度比面向过程高
所有的程序都是由“数据”与“功能”组成,因而编写程序的本质就是定义出一系类的数据,然后定义出一系列的功能来对数据进行操作。在学习“对象”之前,程序中的数据与功能是分离开的
# 数据name、age、sex
name = 'tank'
age = 18
sex = 'female'
# 功能:tell_info
def tell_info(name, age, sex):
print(f'<{name}:{age}:{sex}>')
# 此时若想执行查看个人信息的功能,需要同时拿来两样东西,一类是功能tell_info,另外一类则是多个数据name、age、sex,才能执行,非常麻烦
tell_info(name, age, sex)
二、 类与对象
类就是类别/种类,是面向对象分析和设计的基石
如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能的容器
从两种角度去看待
- 现实世界中
- 现有一个个的对象,经过社会的文明发展,随之总结出类(人类)
- 在程序中
- 必须要先有类,再通过“调用类,产生对象”
强调:在程序中,必须要事先定义类,然后再调用类产生对象(调用类拿到的返回值就是对象)
产生对象的类与对象之间存在关联,这种关联指的是:对象可以访问到类中共有的数据与功能,所以类中的内容仍然是属于对象的,类只不过是一种节省空间、减少代码冗余的机制,面向对象编程最终的核心仍然是取使用对象
- 如何定义类(如何写类并产生对象)
- 先从现实世界中通过一个个对象总结出类
- 然后再定义类,后调用类产生对象
三、面向对象编程
3.1 类的定义与实例化
我们以开发一个清华大学的选课系统为例,来介绍基于面向对象的思想如何编写程序
面向对象的基本思路就是把程序中要用到的、相关联的数据与功能整合到对象里,然后再去使用,但程序中要用到的数据以及功能那么多,我们需要先提取选课系统里的角色:学生、老师、课程等,然后显而易见的是:学生有学生先关的数据与功能,老师有老师相关的数据与功能
# 学生的数据有
学校
名字
年龄
性别
# 学生的功能有
选课
详细的
# 学生1
数据:
学校 = 清华大学
姓名 = 苟汪汪
性别 = 女
年龄 = 28
功能:
选课
# 学生2
数据:
学校 = 清华大学
姓名 = 毛喵喵
性别 = 女
年龄 = 25
功能:
选课
# 学生3
数据:
学校 = 清华大学
姓名 = 牛莽莽
性别 = 男
年龄 = 30
功能:
选课
我们可以总结出一个学生类,用来存放学生们相同的数据与功能
# 学生类
相同的特征:
学校 = 清华大学
相同的功能:
选课
基于上述的分析,我们接下来要做的就是在程序中定义出类,然后调用类产生对象
class Student: # 类的命名应该使用“驼峰体”
school = '清华大学' # 数据
def choose(self): # 功能
print('%s is choosing a course' % self.name)
类体最常见的就是变量的定义和函数的定义,但其实类体可以包含任意Python代码,类体的代码在类定义阶段就会执行,因为会产生新的名称空间来存放类中定义的名字,可以打印Student.__dict__
来查看类这个容器内盛放的东西
print(Student.__dict__)
{'__module__': '__main__', 'school': '清华大学', 'choose': <function Student.choose at 0x0000017AE8879A60>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
调用类的过程称为将类实例化,拿到的返回值就是程序中的对象,或称为一个实例
stu1 = Student() #每实例化一次就得到一个学生对象
print(stu1)
stu2 = Student()
print(stu2)
stu3 = Student()
print(stu3)
<__main__.Student object at 0x0000026C62037BA8>
<__main__.Student object at 0x0000026C6203F198>
<__main__.Student object at 0x0000026C6203F358>
如此stu1、 stu2、 stu3全都一样了(只有类中有的内容,而没有各自独有的数据),想在实例化的过程中就位三位学生定制各自独有的数据:姓名,性别,年龄,需要我们在类内部新增一个__init__
方法
class Student:
school = '清华大学'
# 该方法会在对象产生之后自动执行,专门为对象进行初始化操作,可以有任意代码,但一定不能返回非None的值
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
def choose(self):
print('%s is choosing a course' % self.name)
然后我们重新实例出三位学生
stu1 = Student('苟汪汪', '女', 28)
stu2 = Student('毛喵喵', '女', 25)
stu3 = Student('牛莽莽', '男', 30)
单拿stu1的产生过程来分析,调用类会先产生一个空对象stu1,然后将stu1连同调用类时括号内参数一起传给Student.__init__(stu1,'苟汪汪','女',28)
def __init__(self, name, sex, age):
self.name = name # = '苟汪汪'
self.sex = sex # = '女'
self.age = age # = 28
会产生对象的名称空间,同样可以用__dict__
查看
print(stu1.__dict__)
{'name': '苟汪汪', 'sex': '女', 'age': 28}
3.2 属性访问
3.2.1 类属性与对象属性
在类中定义的名字,都是类的属性,细说的话,类有两种属性:数据属性和函数属性,可以通过__dict__
访问属性的值,比如Student.__dict__['school']
print(Student.school) # 访问数据属性,等同于Student.__dict__['school']
print(Student.choose) # 访问函数属性,等同于Student.__dict__['choose']
清华大学
<function Student.choose at 0x00000207292599D8>
操作对象的属性也是一样的
print(stu1.name) # 查看,等同于obj1.__dict__['name']
'苟汪汪'
stu1.course = 'python' # 新增,等同于obj1.__dict__['course'] = 'python'
stu1.age = 38 # 修改,等同于obj1.__dict__['age'] = 38
del stu1.course # 删除,等同于del obj1.__dict__['course']
3.2.2 属性查找顺序与绑定方法
对象的名称空间里只存放着对象独有的属性,而对象们相似的属性是存放于类中的。对象在访问属性时,会优先从对象本身的__dict__
中查看,未找到,则取类中的__dict__
中查找
1、类中定义的变量是类的数据属性,是共享给所有对象用的,指向相同的内存地址
print(id(Student.school)) #1450119990160
print(id(stu1.school)) #1450119990160
print(id(stu2.school)) #1450119990160
print(id(stu3.school)) #1450119990160
# id 都一样
2、 类中定义的函数时类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
print(Student.choose(stu1)) #苟汪汪 is choosing a course
print(Student.choose(stu2)) #毛喵喵 is choosing a course
print(Student.choose(stu3)) #牛莽莽 is choosing a course
但其实类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同
print(id(Student.choose)) # 1748242242008
print(id(stu1.choose)) # 1748211873224
print(id(stu2.choose)) # 1748211873224
print(id(stu3.choose)) # 1748211873224
绑定到对象的方法特殊之处在于,绑定给谁就应该由谁来调用,谁来调用,就会将谁本身当做第一个参数自动传入(方法__init__
也是一样的道理)
stu1.choose() # 等同于Student.choose(stu1)
stu2.choose() # 等同于Student.choose(stu2)
stu3.choose() # 等同于Student.choose(stu3)
绑定到不同对象的choose技能,虽然都是选课,但苟汪汪选的课,不会选给毛喵喵,这正是“绑定”的精髓
print(list)
<class 'list'>
# 三个对象都有绑定方法append,是相同的功能,但内存地址不同
print(id(l1.append))
print(id(l2.append))
print(id(l3.append))
# 操作绑定方法l1.append(4),就是往l1添加4,绝对不会将4添加到l2或l3
l1.append(4)
print(l1) # [1, 2, 3, 4]
print(l2) # ['a', 'b', 'c']
print(l3) # ['x', 'y']