面向对象目的:
写出有意义的面向对象的代码,其作用就是封装代码
定义时注意:
命名规范 Student、StudentPages
类体不能什么都不写,要写pass
定义示例:
class Student():
# 开始类体的编写
name = ''
age = 0
def print_file():
print('age = '+str(age))
stu = Student() #不需要使用new 来实例化这个类
调用类的方法:
stu.print_file()
注意上述调用会报错
# TypeError: print_file() takes 0 positional arguments but 1 was given
做如下修改:
def print_file(self):
print('age = '+str(age))
仍然报错,报错age没有定义
继续修改,改完正确运行
def print_file(self):
print('age = '+str(self.age))
正确示例:
class Student():
name = ''
age = 0
def print_file(self):
print('age = '+str(self.age))
stu = Student()
stu.print_file()
# 或者直接用:Student().print_file()
注意:
上述类体中,对于print_file函数,不能在类体里调用
写类的模块,最好是只写类,然后通过其他模块来实例化调用什么的
from c1 import Student
Student().print_file()
Student().age
注意:
如果c1.py中同时包含对Student类的实例化和调用,那么上述import时也会执行c1.py中的示例化和调用
所以,最好是模块和类分开,便于调用时的清晰
方法 和 函数:区别:
方法是语言设计层面的考量,应用起来没什么区别
类中的函数应该叫‘方法’,模块中的函数就叫‘函数’
类中的变量应该叫‘数据成员’,模块中变量叫‘变量’
类和对象 通过实例化联系在一起
什么是类:
就是数据及一些操作的有意义的封装,可以体现出数据特征和行为特征
行为要联系主体,体现在类的设计中要具有现实意义
什么是对象:
表示具体的类对象,类本身可以实例化多种多样的对象
通过实例化来创造对象的多样性,依靠类的构造函数实现
class Student():
name = ''
age = 0
def __init__(self): # 至少需要添加self参数
print('init')
def print_file(self):
print('age = '+str(self.age))
stu = Student() #构造函数在实例化时自动调用
stu.__init__()
#构造函数也可以调用,跟普通函数类似,但是不推荐这样用
但是:对于构造函数 只允许返回None,返回其他则报错
为构造函数添加参数:
def __init__(self,param1,param2):
此时实例化类时,必须传入两个值:stu = Student('a','b')
构造函数通常的用法:
来修改类的数据特征,即重置类的成员变量
class Student():
name = ''
age = 0
def __init__(self, name, age): # 至少需要添加self参数
name = name
age = age
上面的代码不报错,但是不能修改name和age的值,并不是因为变量作用域的问题
注意:
类的变量的作用域 与 模块变量的作用域 完全不同!
要注意区别类的行为和模块的行为
类变量 实例变量:代码示例:
class Student():
name = '' name 是类变量:与类相关
age = 0 age 是类变量:与类相关
def __init__(self, name, age):
self.name = name self.name 实例变量:与对象相关
self.age = age self.age 实例变量:与对象相关
s1 = Student('a',1) 作为实例变量传入
s2 = Student('b',8) 作为实例变量传入
注意上述self可以换成任意名称:
def __init__(this, name, age):
this.name = name
this.age = age
换成this也是对的,但是推荐使用默认的 self
使用区别:
对象名.成员变量 取决于实例化时的构造
类名.成员变量 只跟类有关,不可改变
应用场景:
比如定义一个狗类叫做ClassA:
里面有成员变量 动物种类、狗品种、狗毛色
有构造函数,参数为品种、毛色,但动物种类变量就等于“狗”,构造时不修改
实例化时借助构造函数,得到N个不同的狗对象ObjN,可以对应现实世界中不同的狗个体
此时,ObjN.品种,就是此狗对象的对象属性
而ClassA.动物类型,表明此类的特征属性,表示共同特性或者不属于个体特性的变量就可以作为类的成员变量
(类的机制)
类变量和实例变量的特性示例代码:
class Student():
name = '类变量name'
age = 0
def __init__(self, name, age): # 至少需要添加self参数
name = name
age = age
obj = Student('实例变量name','实例变量age')
print(obj.name) #打印类变量name
print(Student.name) #打印类变量name
print(obj.__dict__) #打印{}
print(Student.__dict__) #打印{'name': '类变量name', '__doc__': None, '__weakref__': , '__init__': , '__module__': '__main__', '__dict__':, 'age': 0}
寻找相关变量的机制:
如果尝试去访问对象的一个成员变量
首先会在对象的变量列表obj.__dict__里去查找有没有
否则,到类的变量列表Student.__dict__去寻找
否则继续去类的父类中去寻找
示例代码:
class Student():
name = '类变量name'
age = 0
def __init__(self, name, age): # 至少需要添加self参数
self.name = name
self.age = age
obj = Student('实例变量name','实例变量age')
print(obj.name) #打印类变量name
print(Student.name) #打印实例变量name
print(obj.__dict__) #打印{'age': '实例变量age', 'name': '实例变量name'}
解释:
修改为self.name之后,则是实例的变量,在构造函数中必须赋值给实例的变量
定义实例方法例如构造函数时,需要self出现,但是调用实例方法时不需要出现self
注意:
self和实例、对象绑定,与类无关
实例、对象可以调用的方法叫:实例方法,参数第一个必须为 self保留
self可以换成其他名字,比如this,但位置必须是第一参数
解释:
意思是实例方法第一个参数应该保留,具体叫self还是this或其他无所谓,但推荐用 self
Python的类--- 变量 --- 类变量
--- 实例变量
--- 方法 --- 实例方法 self + .操作符 -> 修改实例变量
--- 类方法
--- 静态方法
--- 构造函数(特殊的实例方法,只是默认调用)
实例方法要操作变量:
--- 实例方法 self + .操作符 -> 修改实例变量
例如:
class Student():
name = '类变量name'
age = 0
def __init__(self, name1, age): # 至少需要添加self参数
self.name = name1
self.age = age
print(self.name) #访问的实例变量
print(name)
#这也是访问的实例变量,但是访问的是形参name,如果形参不是name,那就会报错
#print(__dict__)
obj = Student('实例变量name','实例变量age')
打印:
实例变量name
实例变量name
注意:
查找变量列表__dict__只能在外部调用时访问,在实例方法内无法打印
实例方法中,方法参数不要和类变量名相同
类变量定义时,不要与类内置变量重名
--- 实例方法 修改类变量
例如:
class Student():
name = '类变量name'
age = 0
def __init__(self, name1, age): # 至少需要添加self参数
print('形参name1:'+name1)
self.name = name1
print('修改实例变量:'+self.name)
print('访问类变量法一:'+Student.name)
print('访问类变量法二:'+self.__class__.name) #注意self.__class__的使用
obj = Student('实例变量name','实例变量age')
输出:
形参name1:实例变量name
修改实例变量:实例变量name
访问类变量法一:类变量name
访问类变量法二:类变量name
--- 实例方法操作类变量 完成类变量的变化
class Student():
sum = 0
name = '类变量name'
age = 0
def __init__(self, name1, age): # 至少需要添加self参数
self.__class__.sum += 1
print('类变量sum变为:'+str(self.__class__.sum))
obj1 = Student('Tom',13)
obj2 = Student('Kimmy',24)
obj3 = Student('Jack',18)
输出:
类变量sum变为:1
类变量sum变为:2
类变量sum变为:3
注意:
实例方法通常是操作实例变量的,但是也可以操作类变量,引出:专门操作类变量的方法
类方法:定义规范:
@classmethod #使用装饰器@classmethod来定义一个类方法
def plus_sum(cls): #类方法的参数必须含一个cls参数
pass
重新完成上述类变量的修改:
class Student():
sum = 0
def __init__(self,param):
pass
@classmethod
def plus_sum(cls): # cls仍然可以改成别的名字,不建议更改
cls.sum += 1
print(cls.sum)
类方法的调用
stu = Student(1)
stu.plus_sum() # 打印 1
stu = Student(2)
stu.plus_sum() # 打印 2
stu = Student(3)
stu.plus_sum() # 打印 3
再次强调:
实例方法关联的是对象,类方法关联的是类本身
另外,两者有时候都可以完成参数修改,但是要是操作有“意义”有时就需要区分类方法和实例方法,例如与对象无关的操作就应该使用类方法
即,对象最好不要调用@classmethod 类方法(虽然不报错,但是缺失实际意义)
静态方法定义规范:
@staticmethod
def add(x,y):
print("这是一个静态方法")
与其他方法的区别:
静态方法中没有强制参数
实例方法中,self 参数代表对象本身
类方法中 cls 代表类本身
一个对象或以各类都可以调用静态方法
示例:
class Student():
sum = 0
name ='Lei'
def __init__(self,param):
self.name = param
@classmethod
def plus_sum(cls):
cls.sum += 1
print(cls.sum)
#print(self.name) # 类方法不可以引用实例变量
@staticmethod
def add(x,y):
#print(self.name) # 静态方法不可以引用实例变量
print(Student.sum) # 静态方法可以访问类变量
print('这是一个静态方法')
#调用
stu = Student(1)
stu.add(1,1) # 对象调用静态方法 且访问了类变量 不可以引用实例变量
Student.add(1,1) # 类调用静态方法 且访问了类变量 不可以引用实例变量
stu.plus_sum() # 对象调用类方法 且访问了类变量 不可以引用实例变量
Student.plus_sum() # 类调用类方法 且访问了类变量 不可以引用实例变量
注意:
静态方法不要经常使用,与类的关联性不强,与普通函数无区别
类成员的可见性对于下面示例:
class Student():
sum = 0
def __init__(self,param,param1):
self.name = param
self.age = param1
self.score = '0'
print('初值为:'+self.score)
def marking(self,score):
self.score = score
print('修改后:'+str(score))
def do_hmwork(self):
pass
def do_eng_hmwork(self):
pass
s = Student(1,2) # 将socre参数隐藏,不暴露score的直接赋值
s.score = -1
#不推荐方式,直接修改参数,这样没法进行相关过滤,不应该通过直接访问的方式修改
print('修改后:'+str(s.score))
#正确方法:所有访问应该通过方法操作变量,可以在方法中对输入进行判断,进而保护数据
s1 = Student(1,2)
s1.marking(-1)
注意:
上述marking方法之外,仍然可以通过 s.score = -1 来直接赋值,
原因:
上述变量和方法全部都是公开的
Python控制变量的可见性(读、写): 公开public 私有private
方式:
私有变量:__私有变量名
私有函数:__marking()
注意:
对于构造函数,因为__init__右边也有下划线,这样不会被识别为私有
示例:
class Student():
sum = 0
def __init__(self,param,param1):
self.name = param
self.age = param1
self.__score = '0'
print('初值为:'+self.__score)
def __marking(self,score): #添加双下划线
self.__score = score
print('修改后:'+str(self.__score))
s1 = Student(1,2)
#s1.__marking(-1) # 访问私有方法报错:'Student' object has no attribute '__marking'
s1.__score = -1
print(s1.__score) # 访问私有变量,成功修改
#上述原因是:实际是利用py得动态属性,通过点的方式新添加了一个__score变量,原有私有变量并没有修改
#下面直接访问会发现 访问报错 :'Student' object has no attribute '__score'
s2 = Student(1,2)
print(s2.__score)
可以利用__dict__内容验证:
print(s1.__dict__)
输出:
{'name': 1, '_Student__score': '0', 'age': 2,'_score':-1}
print(s2.__dict__)
输出:
{'name': 1, '_Student__score': '0', 'age': 2}
分析上述发现:
其实私有变量会被改名,此处由__score变为了_Student__score,所以访问原名是访问不到的
比较两次打印,会发现s1.__score = -1 这句话其实会添加一个__score变量,而没有修改原来的score。因为原来的socre已经被改名了
上述发现:
其实Python没有完善的私有变量机制,其仅仅是通过改名,如果使用_Student__score来操作,仍然可以完成修改
面向对象的特性:继承三大特性:继承、封装、多态
封装:类就是从现实世界的角度对变量和方法进行封装,很抽象比较难讲清楚
类的组成:变量和方法
继承作用:避免定义重复的方法和重复的变量
推荐一个模块创建一个类
对于以下示例:
c2模块的Human代码如下:
class Human():
sum = 0
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
print(self.name)
子类Student如下:
from c2 import Human
class Student(Human): # 标准的继承方法
sum = 0
def __init__(self,name,age):
self.name = name
self.age = age
self.__score = 0
print('初值为:'+str(self.__score))
s = Student('Tom',13) # 实例化子类时,要按照父类的构造函数传参
print(s.sum)
print(Student.sum) # 打印 0 表示子类继承了父类的类变量
print(s.name) # 打印 Tom 表示子类继承了父类的实例变量
print(s.age) # 打印 13 表示子类继承了父类的实例变量
s.get_name() # 打印 Tom 表示子类继承了父类的实例方法
注意:
上述只是将Human父类的变量和方法提取到了子类中
Python允许多继承,一个子类可以有多个父类,一般用不到
进一步:
现在子类有自己独有的方法和变量
例如:Student类有school变量,那么其构造函数为school+父类构造参数
在子类里调用父类的函数,示例:
from c2 import Human
class Student(Human): # 标准的继承方法
def __init__(self,school,name,age): # 父类构造参数是name age
self.school = school
Human.__init__(self,name,age) #直接调用父类构造函数 传参
#注意此处父类构造参数要加上self,此处是!普通函数的调用!,传参缺一不可,self必不可少
s = Student('YangTz','Tom',13)
print(s.school) # 正确打印YangTz
print(s.name) # 正确打印Tom
print(s.age)# 正确打印13
开闭原则:
对扩展是开放的,对更改本身是关闭的
注意:
Human.__init__(self,name,age)
上述使用类,调用了实例方法,其实不推荐这样做,如果类调用一个实例方法,那么实例方法的 self 参数会成为一个普通参数,调用时应该被传入方法内
现在对于上述代码,如果父类改变,那么代码中涉及的地方全都要改,违反了开闭原则
引出:super() 通用调用方法,修改为:
super(Student,self).__init__(name,age)
注意:
这样修改父类时不需要修改这里的代码
super()目的是继承父类的同名方法,如__init__()或一些公共方法
对于一个普通实例方法do_something(self),如果其和父类方法同名,那么会优先调用子类的此方法
但是如果修改为
def do_homework(self):
super(Student,self).do_homework()
那么此时表示子类的该实例方法继承了父类的该方法,此时调用会执行父类的do_something()