第九章 高级部分:面向对象
类的定义
类!=面向对象,要有意义的面向对象的代码才行。 面向对象最核心的就是 类和对象。
用 class关键字 +类名 来定义类。
类的名字建议首字母大写,不建议使用下划线。
类的最基本的作用:封装一系列的变量和函数,即封装代码
类只负责定义,不负责运行。不建议把类的定义与使用放在同一个模块。
C7.py
类的定义:
class Student():
name=''
age=0
#在类的内部可以定义若干变量,描述类的特征
def print_file(self):
print('name:'+ self.name)
print('age:'+ str(self.age)
#在类的内部还可以定义函数,要想在后面调用该函数则要用到self关键字,类下的函数叫做类的方法,描述类的行为
如何使用类:
student = Student() #对类进行实例化:变量 = 类名()
student.print_file() #调用类的方法
输出:
name:
age:0
浅谈函数与方法的区别
(1)从另一个模块调用类:
from C7 import Student
student = Student()
student.print_file()
(2)方法与函数的区别
方法:设计层面
函数:程序运行、过程式的一种称谓
在类下定义的 变量 称做是数据成员。类下定义的 函数 称做是方法。
注:初学者不用太纠结这些称谓
类与对象
(1)类的定义:类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起。
(2)类是总称,对象是具体的。类 实例化 就变成了对象。
构造函数
class Student():
name=''
age=0
def __init__(self): #构造函数,注意两边都有有两个横线
print('s')
student1 = Student() #实例化,在Python中不需要加上new
student1.__init__()
输出:
s
s
实例化的时候,python会自动调用构造函数。
构造函数默认返回None,且不能修改为返回其他值。
区别模块变量与类中的变量
(1)思考题:
class Student():
name=''
age=0
def __init__(self,name,age): #构造函数增加了参数,则在实例化时必须传入参数
name = name
age = age
def do_homework(self):
print('homework')
student1 = Student('小李', 18)
print(student1.name)
输出一行空字符串
(2)在Python中,局部变量不能覆盖全局变量,但是这里只打印出空字符串,并不是这个原因:并不是说name=‘’
中的name就是全局变量,而后面构造函数中的name就是局部变量。我们要区别模块变量与类中的变量。真正的原因:与下下节要讲的类与对象的变量查找顺序有关。
类变量与实例变量
class Student():
name='qi'
age=0 #两个都是类变量
def __init__(self,name,age):
# 构造函数
# 初始化对象的属性
self.name = name #第一个name是实例变量,第二个是形参,两者是可以一样的。
self.age = age #实例变量推荐用self来定义,若换成其他的,则前面函数定义的参数中self也要换
def do_homework(self):
print('homework')
student1 = Student('小李', 18)
student2 = Student('小张',18)
print(student1.name)
print(student2.name)
print(Student.name) #打印的是类的变量
输出:
小李
小张
qi
类与对象的变量查找顺序
如果访问一个实例变量,会在对象的实例变量列表里面去查找有没匹配的变量,若找不到,则会继续去类变量列表里面找。仍没有找到的话,会去其父类中查找。
扩展:
print(student1.__dict__)
print(student.__dict__)
self 与实例方法
(1)什么时候用到self?
实例可以调用的方法 叫做实例方法。
在类下定义一个实例方法时,需要传入self 参数。调用时不用传入(因为会默认传入)。(也可以把self改成其他的,但是建议用self。在其他语言中的this就相当于这里的self。)
def __init__(self,name,age):
self.name = name # 用self指定对象的实例变量
self.age = age
print(self.age)
print(self.name)
(2)self 的意义:self 就是 当前调用的某一个方法的 对象。
self 只和对象有关,与类无关。谁调用了self的方法,则self就指代的谁。self 代表的是实例(对象)
例如某类中有该方法:
def do_homework(self):
print('homework')
调用该方法:
student1.do_homoework() ,则此时self就是指的对象student1.
上面例子中:
self.name = ....
相当于在给一个 一个对象.name 赋值 ,也就是在给一个对象的实例变量在赋值
在实例方法中访问实例变量与类变量
(1)目前所学知识框架梳理
(2)在实例方法里面操作实例变量是很容易的,用 self 访问实例变量即可。
def __init__(self,name,age): #构造函数也可看做是一个特殊的实例方法
self.name = name
self.age = age
print(self.age) # 用 self 访问实例变量即可
补充:构造函数与实例方法的区别:
- 构造函数也可看做是一个特殊的实例方法,几乎所有的行为 和 普通的实例方法 是一样的。
- 调用的方式不一样:调用构造函数,是通过 创建对象(类的实例化) 时就调用了的: 对象名 = 类名()
而普通的实例方法是:通过 对象名.方法名() 调用的 - 意义不一样:实例方法主要作用是用来描述类的行为的,而构造函数是用来初始化类的各种特征的
(3)易错点:打印实例变量忘记加 self.
(这里是指在类下的方法里面打印要加self,在类外部调用实例方法时不用传入self参数!)
例子依然是前面所讲到的
def __init__(self,name1,age): #某个类下的构造函数
self.name = name1
self.age = age
print(self.name) —————— 会打印出实例变量
print(name) —————— 会打印出形参name的值
再看一个:
def __init__(self,name,age):
self.name = age # 把年龄赋值给姓名
print(self.name) # 打印实例变量
print(name) # 打印形参的值
student1 = Student('小李', 18)
输出:
18
小李
def __init__(self,name1,age):
self.name=name1
print(self.name)
print(name)
student1 = Student('小李', 18)
输出:
小李
报错信息.....
———————— 这里报错是因为把形参名改为naem1 后,print(name) 无法找到形参。
———————— 我们之前在小节 “类与对象的变量查找顺序 ” 中所说的查找机制并不会生效,该查找机制只会在类的外部,也就是调用类的时候 才会生效。
注:这里很多例子都是部分代码,都是基于 前面几小节中 写的Student类的例子。
(4)在类下的方法里如何访问类变量?———— 类名.类变量
class Student():
sum = 0
def __init__(self,name,age):
self.name = name
print(Student.sum) #访问类变量sum
student1 = Student("小李",18)
输出:0
还有另外一种方法:通过self 来访问:
把 print(Student.sum)
换成 print(self.__class__.sum)
仍然也可以打印出类变量的值。
类方法
(1)体会类变量的作用
class Student():
sum=0
def __init__(self,name,age):
self.name=name
self.age=age
self.__class__.sum += 1
print('当前学生总数为:' + str(self.__class__.sum))
student1 = Student("小李",18)
student2 = Student("小张",18)
student2 = Student("小陈",18)
输出:
当前学生总数为:1
当前学生总数为:2
当前学生总数为:3
———————— 创建一个对象,则类变量sum就加1 。
(2)如何定义类方法?
在定义方法前加上语句:@classmethod
定义类方法时,参数要使用 cls(class的简写)
(3)类方法用于操作类变量
class Student():
sum=0
def __init__(self,name,age):
self.name=name
self.age=age
self.__class__.sum += 1 # 通过self来访问 类变量 sum
print('当前学生总数为:' + str(self.__class__.sum))
@classmethod
def plus_sum(cls): # 定义类方法要使用cls
cls.sum += 1 # 通过 cls 调用 类变量 sum
print(cls.sum)
student1 = Student("小李",18)
Student.plus_sum() # 调用一次 类变量就加1
student2 = Student("小张",18)
Student.plus_sum()
student2 = Student("小陈",18)
Student.plus_sum()
输出:
当前学生总数为:1
2
当前学生总数为:3
4
当前学生总数为:5
6
注:cls 同样地也可更改为其他的 名字。但是建议使用cls
(3)类方法与实例方法的区别
类方法关联的是类本身,实例方法关联的是对象。
两者的根本区别不是 cls 与 self (因为它们都可更改为其他的名字),而是有没有 @classmethod
虽然在实例方法中可以调用类变量,但是有时候在意义上不好理解。
(4)cls 的意义:代表调用的类方法的类
比如上面例子中的 Student.plus_sum()
这里调用的类方法就是plus_sum(),它的类就是Student,则 cls就是代表Student 这个类
(5)Student.plus_sum()
是用类来调用类方法,那么可以用对象调用类方法吗?
答:可以,直接Student1.plus_sum()
即可,会有一样的结果,但是不建议这么做!!!
静态方法
(1)如何定义一个静态方法?
@staticmethod
def add(x,y):
print('This is a static method')
(2)静态方法相关知识点
静态方法是可以被类和对象同时一起调用的。
静态方法内部是可以访问类变量的。
class Student():
sum=0
def __init__(self,name,age):
self.name=name
self.age=age
self.__class__.sum += 1
print('当前学生总数为:' + str(self.__class__.sum))
@staticmethod
def add(x,y):
print(Student.sum) #静态方法访问类变量
print('This is a static method')
student1 = Student("小李",18)
student1.add(1,2)
Student.add(1,2) #类和对象同时调用静态方法
输出:
当前学生总数为:1
1
This is a static method
1
This is a static method
静态方法与类方法类似。
(3)静态 / 类 方法可不可以访问实例变量(self.实例变量名)?
答:不可以直接访问,会报错。
(4)静态方法与类方法的比较?
静态方法访问类变量:类名.类变量名
类方法访问类变量:cls.类变量名
在python中不推荐使用静态方法,因为它和面向对象的关联不大,和一个普通函数没什么区别。
(5)目前所学思维导图:
成员可见性:公开和私有
(1)
内、外调用:在类的外部、内部 调用 方法、变量。
(2)类的数据的不安全性
前面所学的类方法 / 静态方法 / 实例方法等 都可以 直接在外部调用,更改类和对象内部的数据,所以造成了内部数据的不安全性。
(3)
class Student():
sum=0
def __init__(self,name,age):
self.name=name
self.age=age
self.score = 0
self.__class__.sum += 1
def marking(self,score):
if score < 0:
return '不能给别人打负分'
self.score = score
print(self.name + '同学本次考试分数为:' + str(self.score))
student1 = Student("小李",18)
result = student1.marking(-1)
print(result)
这里的marking方法的使用场景:如果这个类是提供给别人使用,他们并不熟悉类内部的机制,有可能会打负分。当有人在外部修改分数为负分时,会输出:不能给别人打负分。
编程提倡的规范:
类下的变量是类非常重要的特征,如果我们要修改类的特征的值,不应该直接通过访问变量的形式来改。而是应该通过方法来完成。
(4)成员的可见性问题:
公开的:一个变量/函数 可以在类的外部直接访问
私有的:在类的外部无法访问,既不能设置也不能读取。
如何变成私有的?
一般的,变量 / 方法 名前加了双下划线"__",就认为是私有的(在名字后面不用加)。构造函数是前后都有双下划线(__init__
),是公开的。
没有什么是不能访问的
(1)对一个私有的方法从外部调用时会报错,但是从外部调用 私有的变量 不会报错,为什么?
探索一:
class Student():
sum=0
def __init__(self,name,age):
self.name=name
self.age=age
self.__score = 0
self.__class__.sum += 1
def marking(self,score):
if score < 0:
return '不能给别人打负分'
self.__score = score
print(self.name + '同学本次考试分数为:' + str(self.__score))
student1 = Student("小李",18)
student1.__score = -1
print(student1.__score)
输出:
-1
从外部调用 私有的实例变量 没有报错,是由于python语言的动态性,student1.__score = -1 实际上是给对象student1新添加了一个实例变量:__score
探索二:
同样是上面的类,重新实例化一个对象student2:
student1 = Student("小李",18)
student2 = Student("小陈",18)
student1.__score = -1
print(student1.__score)
print(student2.__score)
输出:
-1
报错信息。。。。
—————— 对象student2 调用私有变量__score却报错了,student1没报错是因为我们上面所说的student1.__score = -1给给对象student1新添加了一个实例变量:__score。
而student2没有添加变量__score,所以说找不到该变量。总的来说这里的__score和之前设的私有变量不是同一个变量了。
探索三:验证
同样是上面的类:
student1.__score = -1
print(student1.__dict__)
print(student2.__dict__)
输出:
{'name': '小李', 'age': 18, '_Student__score': 0, '__score': -1}
{'name': '小陈', 'age': 18, '_Student__score': 0}
可以看出:
原先的私有变量变成了'_Student__score',所以说会报找不到私有变量的错误。
student1重新生成了一个变量并赋值为-1:'__score': -1
结论:python对私有变量的保护机制实际上就是把 定义的私有变量名进行了更改,让你找不到原来的变量。也就是说实际上在在Python中没有真正的私有变量。
应该要养成自觉不 更改/读取 私有变量的习惯。
继承(重点)
(1)什么叫做继承?
C7模块:
class Human():
pass
from C8 import Human # 先导入
class Student(Human): # 再继承,
# Human此时称为父类,Student为Human的子类
sum = 0
def __init__(self,name,age):
self.name = name
self.age = age
self.__score = 0
self.__class__.sum += 1
def do_homework(self):
print('homework')
(2)子类继承父类的变量和方法。
C8模块(父类):
class Human():
sum=0
def __init__(self,name,age):
self.name = name
self.age = age
def get_name(self):
print(self.name)
C7模块(子类):
from C8 import Human
class Student(Human):
pass
print(Student.sum)
输出:0
继承了父类的类变量sum
继续补充子类的代码:
from C8 import Human
class Student(Human):
pass
student1 = Student('小李',18)
print(student1.sum)
print(Student.sum) #继承类变量
print(student1.name) #继承实例变量
print(student1.age)
输出:
0
0
小李
18
父类的变量和方法是都可以被继承的。
(3)在其他编程语言中一般是单继承(一个子类仅有一个父类)。但在python中可以多继承(一个子类可以有多个父类)。
建议先把单继承用好!
用一张图表示单继承关系:
(4)
之前说过 在实例化的时候调用实例变量时,是不用我们传入self的。但在这里要传。(注:定义的时候不管哪种情况都要传入self)
from C8 import Human
class Student(Human):
def __init__(self,school,name,age) #因为父类的构造函数有name,age参数,所以这里也要传入
self.school = school
Human.__init__(self,name,age) #调用 父类的构造函数,要传入self不然会报错
student1 = Student('第一中学','小李',18)
print(student1.name)
print(student1.age)
子类方法调用父类方法:super关键字
(1)如何在子类中调用父类的构造函数?(了解即可)
上一节中讲到的Human.__init__(self,name,age)
,要传入self。是因为这是通过一个类来调用的,就是一个普通的方法调用。
student1 = Student('第一中学','小李',18)
这是进行实例化,是会自动调用构造函数的,不用传入self。
用对象调用实例方法,不用传入self。
总的来说,用类来调用实例方法,则self就变成了一个普普通通的参数,所以调用时要传入一个参数。但是不推荐用子类方法调用父类的方法。(父类名称更改后还要更改这里的代码,极其地不方便!)
(2)另外一种 调用父类方法的方式?(重点)
super(Student,self).__init__(name,age)
(super实际上代替的是父类名Human)
from C8 import Human
class Student(Human):
# sum = 0
def __init__(self,school,name,age):
self.school = school
# Human.__init__(self,name,age) #调用父类的构造函数 方法一
super(Student,self).__init__(name,age) #调用父类的构造函数 方法二
def do_homework(self):
print('homework')
student1 = Student('第一中学','小李',18)
print(student1.name)
print(student1.age)
输出:
小李
18
(3)子类和父类函数同名的情况:不会报错,并会优先调用子类方法。
C8:
class Human():
sum=0
def __init__(self,name,age):
self.name = name
self.age = age
def get_name(self):
print(self.name)
def do_homework(self):
print('This is a parent method')
from C8 import Human
class Student(Human):
def __init__(self,school,name,age):
self.school = school
super(Student,self).__init__(name,age) #调用父类的构造函数
def do_homework(self):
super(Student,self).do_homework() #用super关键字调用父类的实例方法
print('homework')
student1 = Student('第一中学','小李',18)
student1.do_homework()
总结:
super:用于调用父类 构造函数 和 实例方法。
用法:super (子类名,self) . 方法名 (参数1,参数2…)