类的定义
面向对象两个核心:类、对象
在python中使用class定义类:
class Student(): #类名最好定义为大写开头
name = '' #数据成员
age = 0
def print_file(self): #方法
print('name:' + self.name) #通过self关键字来使用变量
print('age:' + str(self.age))
class StudentHomework():
homework_name = ''
student = Student() #实例化类为对象
student.print_file() #调用类下面的方法
类的最基本作用就是在封装代码。
类的内部不能调用类的方法,类只负责去描述和定义,而不负责执行,运行和调用类需要放在类的外部。
不推荐在一个模块(文件)中既定义类又使用类。
浅谈函数与方法的区别
在一个模块中调用另一个模块的类:
#nine.c2.py
from c1 import Student
Student = Student()
Student.print_file()
方法和函数的区别:
- 方法是设计层面上的称谓,更多的是面向对象中的概念;
- 函数是程序运行的,面向过程的一种称谓。
- 没有必要特别的去区分方法和函数。
变量出现在类中更多称为数据成员。
类与对象
类和对象是通过实例化关联在一起的
类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据的操作封装在一起
数据成员刻画对象的特征,方法刻画对象的行为。
行为要属于主体,因此将上面的类修改为:
class Student(): #类名最好定义为大写开头
name = ''
age = 0
def do_homework(self): #print_file不是学生主体的行为
print('homework')
类被实例化了之后变成具体的对象,若不具体的话就不是对象。
类相当于模板,通过类产生不同的对象。
构造函数
实例化Student 类为三个对象,三个对象是不同的
student1 = Student()
student2 = Student()
student3 = Student()
print(id(student1))
print(id(student2))
print(id(student3))
#2581261097560
#2581261097616
#2581261097672
__init__ 称为构造函数,在实例化对象时自动进行,我们也可以主动调用构造函数(实际代码中很少用到),如果显示的调用构造函数,返回的是None,并且Python规定,构造函数只能返回None,如果自己强制返回别的,会报错
如下只会返回空?
class Student(): #类名最好定义为大写开头
name = '' #类变量
age = 0
def __init__(self, name, age):
#构造函数
#初始化对象的属性
name = name #第一个name代表数据成员,第二个代表构造函数传入的形参
age = age
student1 = Student('Julia', 18)
print(student1.name)
#
类变量与实例变量导致如上问题,详细看下面
补充原因:在尝试访问实例变量时,在实例变量列表里查找,如果没有会到类变量里面寻找。如果类中没有会到父类寻找。
区别模块变量与类中的变量
局部变量不会覆盖全局变量
c = 50
def add(x,y):
c = x + y #这个c不会覆盖全局变量的c
print(c)
不能把模块的机制与类的机制混淆
类变量与实例变量
类变量与实例变量区别:
- 类变量:与类关联,有类模板 Student创建
- 实例变量:与对象关联
对于上面返回空的问题,使用self可以解决:
class Student():
sum = 0 #类变量,表示一个班级所有学生的总数
name = '' #这是类变量,不可以保存对象的特征值
age = 0
def __init__(self, name, age):
self.name = name #在Python中使用self.name 来保存对象的特征值
self.age = age
student1 = Student('Julia', 18) #传递的对象特征值必须保存在对象的内部
student2 = Student('Victoria', 20)
print(student1.name)
print(student1.age)
print(student2.name)
print(student2.age)
# Julia
# 18
# Victoria
# 20
其中self不能称作关键字,但 self 可以定义为任意你想定义的标识
为什么Python中要有类变量和实例变量?类Student是一个抽象的学生,如上定义了 sum ,就是类变量,是对应于整个类的;而不同的学生对象有不同的属性,这时候就是 实例变量的作用了
类与对象的变量查找顺序
__dict__ 打印一个保存当前对象内所有变量的字典
class Student():
name = '123' #定义变量
age = 0
def __init__(self,name,age):
name = name #并没有赋值给student1
age = age
student1 = Student('tai',18)
print(student1.name,student1.age)
print(student1.__dict__)
#123 0
#{}
此时打印的是空字典,说明实例变量赋值没有成功
加上self,变为给实例变量赋值时,显示如下:
class Student():
name = 'ss'
age = 0
def __init__(self, name, age):
self.name = name
self.age = age
student1 = Student('julia', 18)
print(student1.__dict__)
print(Student.__dict__) #打印类中变量
# {'name': 'julia', 'age': 18}
# {'__module__': '__main__', 'name': 'ss', 'age': 0, '__init__': <function Student.__init__ at 0x000001B9CBBDA9D8>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
self与实例方法
实例方法注意:
- 第一个传入的参数为 self
- 如果在类中定义实例方法的话,要固定在类中放上self;在调用方法时,不用传入 self
- self 可以使用任意值,但是Python建议使用self
- 实例方法是实例可以调用的方法,和对象实例相关联。
其中 self 表示:当前调用某一个方法的对象;self 并不属于Python中的一个关键字,因为换成其他任意值均可
在实例方法中访问实例变量与类变量
到目前为止,我们学习的内容思维导图
变量:表示特征
方法:表示行为,操作变量
在方法中访问变量:
class Student():
name = 'ss'
age = 0
def __init__(self, name, age):
self.name = name
self.age = age
print(name) #读取形参name
print(self.name) #读取对象的实例变量,最好使用这种方式
student1 = Student('julia', 18)
#julia
#julia
在实例方法中访问类变量:
class Student():
name = 'ss'
age = 0
sum1 = 0
def __init__(self, name1, age):
self.name = name1
self.age = age
print(Student.sum1) #第一种访问类变量的方式
print(self.__class__.sum1) #第二种访问类变量的方式
student1 = Student('julia', 18)
类方法
类变量的使用场景:
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
self.__class__.sum1 += 1
print('当前学生总数为: ' + str(self.__class__.sum1))
student1 = Student('julia', 18)
student2 = Student('julia', 18)
student3 = Student('julia', 18)
# 当前学生总数为: 1
# 当前学生总数为: 2
# 当前学生总数为: 3
也可以除了构造函数,在其他实例方法找中调用类变量
使用类方法操作类变量:
- 其中第一个参数 cls 也可以使用其他变量名,cls 表示:当前调用的类
- 法之前加上装饰器@classmethod 决定了该方法是类方法
- 实例方法:与对象本身关联;类方法:与类关联
- 也可以使用对象来调用类方法,但是建议不要如此,因为逻辑上不通
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
#self.__class__.sum1 += 1
#print('当前学生总数为: ' + str(self.__class__.sum1))
@classmethod #装饰器,方法之前加上这个装饰器决定了该方法是类方法
def plus_sum(cls):
cls.sum1 += 1
print(cls.sum1)
student1 = Student('julia', 18)
Student.plus_sum()
student2 = Student('julia', 18)
Student.plus_sum()
student3 = Student('julia', 18)
Student.plus_sum()
# 1
# 2
# 3
静态方法
静态方法特征:
- 添加装饰器 @staticmethod
- 没有默认必须传递的参数,比如 self 或者 cls
- 类和对象均可以调用静态方法
- 类方法、静态方法均不可访问实例变量
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
#self.__class__.sum1 += 1
#print('当前学生总数为: ' + str(self.__class__.sum1))
@classmethod #装饰器,方法之前加上这个装饰器决定了该方法是类方法
def plus_sum(cls):
cls.sum1 += 1
print(cls.sum1)
@staticmethod
def add(x, y):
print('This is a static method')
student1 = Student('julia', 18)
student1.add(1,2)
Student.add(3,4)
#This is a static method
#This is a static method
能用静态方法的时候都可用类方法替代,最好不要经常使用,与类和对象的关联性非常弱,和普通的函数几乎没有区别。
类中的所有变量、类到此结束了
成员可见性:公开和私有
方法、变量的内部与外部调用:
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
#self.__class__.sum1 += 1
#print('当前学生总数为: ' + str(self.__class__.sum1))
def do_homework(self):
self.do_english_homework() #方法的内部调用
print('homework')
def do_english_homework(self):
print('english_homework')
student1 = Student('julia', 18)
student1.do_homework() #方法的外部调用
#english_homework
#homework
当前情况下的数据成员、方法等都可以实现对象student1 的外部调用,这样一来类不安全;因为存在有些变量不可以被外部对象随意更改
Python中变量更改的规范:如果需要修改变量的值,定义方法来完成,这样可以在方法中对于参数做判断,最好不要用student1.score = -1 来赋值,如下例子中为对于 score 的修改:
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
self.score = 0
def marking(self, score):
if score < 0:
return '分数不可为负'
self.score = score
print(self.name + '同学的考试成绩为:' + str(self.score))
student1 = Student('julia', 18)
result = student1.marking(-1)
print(result)
student2 = Student('julia', 18)
student2.marking(60)
#分数不可为负
#julia同学的考试成绩为:60
目前score是公开的public——类的外部可以直接访问。
私有的private——不可以直接访问。
不加双下划线:公开
加双下划线:私有
前后都有双下划线:公开,是python内置函数的命名风格。
没有什么是不能访问
尝试从外部访问私有变量:(基于Python动态语言的特性)
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
self.__score = 0
student1 = Student('julia', 18)
student1.__score = -1 #没有报错
#是因为这样操作会给student1对象添加一个新的属性,这并不是我们之前定义的实例变量中的__score
print(student1.__score)
student2 = Student('julia', 18)
print(student2.__score) #报错
# -1
# Traceback (most recent call last):
# File "c1.py", line 76, in <module>
# print(student2.__score)
# AttributeError: 'Student' object has no attribute '__score'
我们也可以用 __dict__ 来证明这一点:
其中'_Student__score' 表示我们定义的实例变量,它是私有的,Python在dict 中展示为'_Student__score';这也就是为什么我们在对私有变量更改时,会新添加一个属性,因为Python类内部已经把私有变量的名字改了
'__score': -1 就是我们student1.__score = -1 赋值(动态添加)之后的结果
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
self.__score = 0
student1 = Student('julia', 18)
student1.__score = -1 #没有报错, 是因为这样操作会给student1对象添加一个新的属性,这并不是我们之前定义的实例变量中的__score
print(student1.__dict__)
#{'name': 'julia', 'age': 18, '_Student__score': 0, '__score': -1}
由此可以向下延申:单下划线 + 类名 + 双下划线 + 实例变量名
通过这种方式可以间接的读取私有变量:
student2 = Student('julia', 18)
print(student2._Student__score)
因此,Python私有变量的访问全靠自觉
继承
面向对象三大特性:继承性、封装性、多态性。
最难讲清楚的反而是封装性,而继承性和多态性都是语法问题。
继承性:避免我们定义重复的方法和重复的变量。
建议一个模块写一个类。
继承的简单例子:
#nine/c4.py
class Human():
sum = 0
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
print(self.name)
#nine/c3.py
from c4 import Human
class Student(Human):
def do_homework(self):
print('homework')
print(Student.sum) #通过类调用类变量
student1 = Student('julia', 18) #继承至父类Human的__init__,需要录入name和age两项
print(student1.sum) #通过对象的方式调用类变量
print(student1.name) #变量继承
student1.get_name() #方法继承
#0
#0
#julia
#julia
子类没有sum,由于其继承了父类的sum,所以能够打印出来。
python的继承有一个特点是:可以多继承(一个子类继承多个父类)
子类中定义实例变量,子类中调用父类的构造函数(不推荐):
#nine/c4.py
class Human():
sum = 0
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
print(self.name)
#nine/c3.py
from c4 import Human
class Student(Human):
def __init__(self, school, name, age):
self.school = school
Human.__init__(self, name, age) #子类中调用父类的构造函数,注意self要加上
#self 必须加的原因是:这里我们使用类Human来调用方法init,但self本身表示的是当前调用方法的实例对象;
#像我们之前的方式,用对象 student1 来调用方法,就不用传入 self
def do_homework(self):
print('homework')
student1 = Student('西安小学','julia', 18) #继承Human,因此必须传入参数,不传报错
print(student1.school)
print(student1.name)
#西安小学
#julia
不推荐原因:尽管我们在子类中实现了父类构造函数的调用,但是Human.__init__(self, name, age) 中 Human 是一个类,为抽象的,init方法是实例方法,使用一个类调用实例方法是说不通的。但是Python是灵活的,这种方式是可以运行的
一个类去调用一个实例方法实际上是不正确的。
from c3 import Human
class Student(Human):
def __init__(self,school,name,age):
self.school = school
Human.__init__(self,name,age) #调用父类初始化要加self
def do_homework(self):
print('done')
student1 = Student('春田花花幼儿园','tai',18) #调用构造函数不需要加self
student1.do_homework() #通过对象调用不用写self
实例化的时候调用构造函数时是python内部构造机制自动调用构造函数,它会帮助我们补全前面的self;
而在子类__init__中调用父类的__init__是自己通过类(而不是实例化的对象)去调用,python不会帮你补全前面的self。
如果通过对象调用,self的实质就是实例对象,所以不要写self。
验证:
from c3 import Human
class Student(Human):
def __init__(self,school,name,age):
self.school = school
Human.__init__(self,name,age)
def do_homework(self):
print('done')
student1 = Student('春田花花幼儿园','tai',18)
Student.do_homework() #通过类去调用实例方法
#Traceback (most recent call last):
# File "c1.py", line 13, in <module>
# Student.do_homework()
#TypeError: do_homework() missing 1 required positional argument: 'self'
#报错,缺少self。
开闭原则:对代码扩展开放,对代码更改关闭。
子类方法调用父类方法:super关键字
from c4 import Human
class Student(Human):
def __init__(self, school, name, age):
self.school = school
super(Student, self).__init__(name, age) #目前主流方法
def do_homework(self):
print('homework')
student1 = Student('西安小学','julia', 18) #继承Human,因此必须传入参数name, age
print(student1.school)
print(student1.name)
#西安小学
#julia
当子类与父类的方法重名时,不会报错,而是会按照子类执行;
这时候我们可以使用super来执行父类的方法;因此super 不仅仅用于构造函数,还可以用于普通的实例方法:
#nine/c4.py
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')
#nine/c3.py
from c4 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()
print('homework')
student1 = Student('西安小学','julia', 18) #继承Human,因此必须传入参数name, age
student1.do_homework()
#This is a parent method
#homework