面向对象的两个概念
- 类和对象
1.类class
- 一个模块里面可以定义多个类 ,但是一个类只能在一个模块里面定义
类的命名规范(与变量的命名规则不同)
1.首字母大写
2.用多个英文单词组合命名时,不建议用下划线,而用驼峰命名法,例如:StudentUser;
如:class StudentUser():
类定义后可以填哪些内容:
1.可以定义若干个变量
2.可以定义若干个函数
举例:
class Student():
name = 'ZXY'
age = 18
def print_file():
print('name:'+name)
print('age:'+str(age))
如何引用一个类:
实例化一个类,得到一个对象
举例:
student1 = Student() ----- 此时,student1就是一个对象
如何调用一个类中的方法:
思考:为什么叫方法,不叫函数(不必过于纠结)
对象名.函数名()
举例:
student1.print_file()
尝试运行之后,发现会报错
原因:print_file()
函数定义时,没有加self
关键字
在函数的列表里一定要加入
self
def print_file(self):
同样函数里引用的变量,也要加self
来引用
print('name:'+self.name)
print('age:'+str(self.age))
完整的代码应该是这样:
class Student():
name = 'ZXY'
age = 18
def print_file(self):
print('name:'+self.name,end=' ')
print('age:'+str(self.age))
student1 = Student()
student1.print_file()
#输出:name:ZXY age:18
注意:调用方法不能在类的内部调用,因为类只负责定义,不能执行;类的作用是封装,如果把执行也放入类里面,那这个封装就毫无意义
在外部模块里调用这个模块的类
假设这个模块名字叫test
from test import Student
student2 = Student()
student2.print_file()
回应上面的思考:为什么叫方法,不叫函数
在C和C++中,称为函数;在java和C#中,称为方法
方法:更多是设计层面,面向对象的开发,需要更多结构和封装的设计
函数:更多是过程层面,面向过程
同样,类里面的变量也不称为变量,而叫做数据成员
类的基本定义:
类是现实世界或思维世界中的实体在计算机中的反映
它将数据以及这些数据的操作封装在一起
还是以上面那段代码为例:
class Student():
name = 'ZXY'
age = 18
行为
和 特征
思考:把print_file
这个方法放在Student
这个类下面合适吗
答:不合适,因为
print_file
的主体不是Student
,而应该是Printer
行为没有搞对主体
是很多情况设计面向对象时出现的问题
def print_file(self):
print('name:'+self.name,end=' ')
print('age:'+str(self.age))
修改后重新设计:
class Student():
name = 'ZXY'
age = 18
def say_hello(self):
print('hello, my name is ' + self.name)
2.对象
上面的的将一个类实例化,就得到一个对象
student1 = Student()
类更像一个模板,可以创造出各个不同的对象,但这些对象都有共同的特性
但就上面的代码,创造的student1、student2、student3,它们的值在逻辑上都是相同的,但在计算机里面它们的id并不同
那怎么让这三个对象在实例化之后变得不同呢
直接用关键字参数改实例化的参数是不行的
student1 = Student(name='ABC',age=15)
---------错误的示范
要在原来的类里面定义另外的函数
__init__
这个名字是固定的,不能改,一般称之为构造函数
class Student():
name = 'ZXY'
age = 18
def __init__(self): //构造函数,同样不能忘记加入self
print('student')
def say_hello(self):
print('hello, my name is ' + self.name)
student1 = Student()
student1.__init__()
#输出:student
student ----------思考:为什么会输出两遍
答:在定义构造函数的时候会自动调用一次,所以后面很少会手动再去调用这个构造函数
进一步思考:a = student1.__init__();print(a)
会输出什么
None
-----因为上面的__init__
中没有return值
设置了
return 'StudentUser'
,结果报错了
说明,构造函数本身不允许返回None
以外的值
构造函数的作用
如何输出任意的变量值,即创造不同的对象
class Student():
name = 'ZXY'
age = 18
def __init__(self,name,age):
#可以在构造函数里放入前面的变量,在实例化的时候就可以改变变量的值
print('student')
def say_hello(self):
print('hello, my name is ' + self.name)
student1 = Student(name='ABC',age=12)
#一定要传入前面构造函数里的两个变量,不传入还保持空会报错
student1.say_hello()
#输出:student
hello, my name is ZXY
#还是会输出一次__init__里的内容,因为构造函数里有print命令,实例化的时候会先执行构造函数
#而且name值还是没改变
优化:
在构造函数里初始化类的特征值
class Student():
name = 'ZXY' # ?全局变量
age = 18
def __init__(self,name,age):
name = name
#这句话的意思就是,把后面'name'这个参数值传给前面这个'name'变量
//局部变量,局部变量并不会覆盖全局变量
age = age
#后面这个参数就来源于def __init__(self,name,age)括号里这个,尽量就保持和前面的变量同名
def say_hello(self):
print('hello, my name is ' + self.name)
student1 = Student('ABC',12)
print(student1.name)
#输出:ZXY
不是想要的ABC,思考问题,创造函数的实例变量要加self
全局变量和局部变量
变量的作用域
c = 50
def add(x,y):
c = x + y
print(c)
add(10,20)
Sprint(c)
#输出:30
50
由于在函数内部有print命令,所以先输出了c=x+y的结果,再输出了全局变量c
说明内部变量c
没有覆盖全局变量c
,这两个是互相独立的变量,互不影响
说明:在函数内部定义的变量,在函数外部是无法访问的,函数内部定义的变量,在函数外部是无法访问的
如果要把外部的全局变量也改变,需要在函数内部声明c是全局变量:用global
c = 50
def add(x,y):
global c
c = x + y
print(c)
add(10,20)
print(c)
#输出:30
30
全局变量改变了
类中的类变量和实例变量
class Student():
name = 'ZXY' #类变量,不同对象的特征值不能改变这个变量
age = 18
#和其他语言不同,其他语言这个位置放的是实例变量,但是Python里,这个位置放的是类变量
def __init__(self, names, ages):
#改为names和ages在这里更有助于理解,实际应用中还是要和前面的变量名一致
self.name = names
#实例变量,使用self.name,来保存name这个特征值
#self这个值不是固定不变的,可以取任意的名字,但后面都要保持一致
self.age = ages
#实例变量只和对象相关,和类无关
student1 = Student('ABC', 8)
print(student1.name)
print(Student.name)
#输出:ABC
ZXY
说明Student类里面的变量name没变,但是student1的name变了
print(student1.__dict__)
#输出:{'name': 'ABC', 'age': 8} ---------不是names和ages,而是类变量的名字
在创造函数中,print(name)-------会报错;
print(names)-------输出ABC;
print(self.name)-------输出ABC
name和age都应该是实例变量,不应该放在类变量这个抽象的描述里,类变量不应该设置为具体的特征值,应该是更宏观上的东西,比如sum总数
在构造函数中引用类变量除了print(self.name)
还可以这么写:
1.
print(Student.name);
2.print(self.__class__.name)
举例:
class Student():
sum1 = 0
def __init__(self, names, ages):
self.name = names
self.age = ages
Student.sum1 += 1
#也可以写成:self.__class__.sum1 += 1;
#但不能写成:self.sum1 += 1 ; 猜测是self引用不能用于计算
print('当前班级总人数为: '+self.sum1)
#也可以写成:self.__class__.sum1 以及 Student.sum1
def say_hello(self):
print('hello, my name is ' + self.name)
student1 = Student('A', 8)
student2 = Student('B', 8)
student3 = Student('C', 8)
#报错---------因为self.sum1是int类型
#应该改为str类型:print('当前班级总人数为: '+str(self.sum1))
#输出:当前班级总人数为: 1
当前班级总人数为: 2
当前班级总人数为: 3
#如果写成 self.sum1 += 1-----------返回结果都是1
类方法:@classmethod
类方法可以用来直接调用类变量,而不需要实例化,类方法以
@classmethod
开头,类方法用于操作类变量
语法糖
、装饰器
用类方法实现上面的在实例方法里增加学生数的操作:
calss Student():
sum1 = 0
@classmethod #这个在python里称为 装饰器
#要有@classmethod,否则下面定义的不是类方法,而是实例方法,因为self是可以改变名字的
def plus_sum(cls):
cls.sum1 += 1
print('当前班级总人数为: '+str(cls.sum1))
#但是这个和构造函数不同,类方法不能直接调用,需要用类名调用,所以要写成:Student.plus_sum(),用类去调用类方法
student1 = Student()
Student.plus_sum()
#输出:当前班级总人数为: 1
也可以用对象调用类方法,是可以运行的,但逻辑上说不通,最好不要用
静态方法:@staticmethod
静态方法不像类方法和实例方法,它不需要创建cls,self这样的名字,cls代表类本身,self代表对象本身
class Student():
@ststcimethod
def add(x,y):
return x + y
student1 = Student()
#类和对象都可以调用静态方法:
Student.add(1,2)
student1.add(1,2)
类方法和静态方法都不能引用实例方法里的变量,即都不能使用self.name引用实例变量name,但可以引用类变量
- 静态方法功能基本和类方法一致,但是和面向对象的关联性比较弱,不要经常去使用静态方法
变量的内部调用和外部调用
class Student():
name = 'ZXY'
age = 18
def __init__(self,name,score):
self.name = name
self.score = 0
def marking(self,score):
self.score = score
print(self.name + '的分数为:' + str(self.score))
student1 = Student('abc',90) #注意第二个参数是score,而不是age
#外部调用:student1.score = -1 -------外部调用变量是很不安全的
#内部调用:student1.marking(60) ------内部调用就不用直接改变量值,是调用的方法,因为方法里可以用条件语句控制输入的值的范围
例如:
class Student():
name = 'ZXY'
age = 18
def __init__(self,name,score):
self.name = name
self.score = 0
def marking(self,score):
if score < 0:
return '不能打负分'
self.score = score
print(self.name + '的分数为:' + str(self.score))
student1 = Student('abc',16)
result = student1.marking(-1)
print(result)
#输出:不能打负分
#所以用调用方法的方式改变量更加安全
但是现在问题是,即使设置了一个marking函数,还是不能防止别人从外部更改内部变量,有没有什么方法能防止外部读取变量呢?
成员的可见性
概念:公开的public和私有的private,公开的函数或者变量是可以在外部读取或者更改的,而私有的函数和变量是无法在外部读取和更改的
在其他语言里设置变量都需要在前面加上public和private,明确的表示了这个变量是公开的还是私有的,但是python不是,所以其他语言是更加直观的
python里面,创建的函数名称和变量名称前面,如果没有双下划线
__
,就说明这个函数和变量是公开的,加了__
才是私有的,构造函数__init__
除外,其实前后都加__
反而不是私有的
举例:
class Student():
name = 'ZXY'
age = 18
def __init__(self,name,score):
self.name = name
self.score = 0
def __marking(self,score):
if score < 0:
return '不能打负分'
self.score = score
print(self.name + '的分数为:' + str(self.score))
student1 = Student('abc',90)
result = student1.__marking(-1)
print(result)
#结果为报错,说__marking函数不存在
但是这样就不能调用marking函数
了,如果不是把marking函数
变为私有,而是把score变量
变为私有呢
举例:
class Student():
name = 'ZXY'
age = 18
def __init__(self,name,__score):
self.name = name
self.__score = 0
def marking(self,score):
if score < 0:
return '不能打负分'
self.__score = score
print(self.name + '的分数为:' + str(self.__score))
student1 = Student('abc',90)
student1.__score = -1
print(student1.__score)
还是能输出 -1 ; 这是为什么呢?
实际上并不是score
没有变成私有变量,而是由于python的动态特性,在student1.__score = -1
的时候,给student1添加了一个新的属性__score
student2 = Student('qwe',90)
print(student2.__score)
#说找不到__score,说明上面的__score并不是全局变量,而是student1的局部变量,也就是说函数里的__score其实并没有改变
print(student1,__dict__)
#现在就可以更明显的看出来了,原来的__score私有变量被改名为'_student__score',而多出了一个'__score'的变量,它的值为-1
其实这里就可以看出来所谓的私有变量到底是这么回事了,是把原来的
__变量名
改名为了_类名__变量名
,所以在外部调用原来的变量名是提示找不到该变量
没绷住,python的私有转换实在太捞了,只能靠自觉,不去访问
面向对象三大特性
1.继承性
2.封装性
3.多态性
1.继承性
举例:
在H.py
模块里建立了类Human
, 在S.py
模块的类Student
里继承类Human
from H import Human
class Student(Human):
#上面就是最标准的继承表示方法,Human是Man的父类,Man是Human的子类
#在 M.py模块里可以直接引用Human里的变量
#而且在实例化的时候要输入Human里的必要参数
继承的好处就是一个父类可以被多个子类引用,不必重复书写
一个子类可以继承多个父类,但需要保证不出问题
H.py
的内容:
class Human():
sum = 0
def __init__(self,name,age):
self.name = name
self.age = age
def print_name(self):
print(self.name)
S.py的内容:
from H import Human
class Student(Human):
def __init__(self,school,name,age): #这里记得也要加入父类里要调用的变量,这里是name
self.school = school
Human.__init__(self,name,age) #可以直接引用类Human里的方法以引用变量,格式为:父类名.父类函数名(self,xxx,xxx) ; 记得这里依然要加self,不然会报错
def do_homework(self):
print('homework has been done')
student1 = Student('人民路小学','小红',10)
print(student1.name)
print(student1.age)
父类名.父类函数名(self,xxx,xxx)
这个方法最好不要用,因为如果这个子类Student更换了父类,而下面有很多方法都调用了父类里的方法,要改非常多的地方,非常不建议这么做
关于self:
上面的
父类名.父类函数名(self,xxx,xxx)
,是必须要加self
的,而用实例化的对象去引用函数的时候是不用加self
的,因为self
就是指代的对象,在用类调用的时候,是没有指定对象的,所以要加self
可以用Student.do_homework()
这样来调用试试,肯定会报错,提示没有传入self
但可以Student.do_homework(student1)
,可是这样完全是多此一举,还是建议用student1.do_homework()
优化 S.py
:
from H import Human
class Student(Human):
def __init__(self,school,name,age):
self.school = school
super(Student,self).__init__(name,age)
#推荐使用这种方法调用父类里的方法,格式为:super(子类名,self).父类函数名(xxx,xxx,xxx),要包含所有父类方法里的变量,不然会报错
def print_name(self):
#子类里的方法可以和父类里的方法同名,只是调用方法不同
print('my name is ' + self.name)
student1 = Student('人民路小学','小红',10)
student1.print_name()
#思考:这里是调用了Human里的print_name 还是Student里的print_name
#输出:my name is 小红 ----------- 说明调用了Student里的print_name
那怎么调用Human
里的print_name
呢?
from H import Human
class Student(Human):
def __init__(self,school,name,age):
self.school = school
super(Student,self).__init__(name,age)
def print_name(self):
super(Student,self).print_name() # ?这是方法,在执行student1.print_name()的时候会按这里的顺序执行print_name;这里是先执行父类的print_name,再执行子类的
print('my name is ' + self.name)
student1 = Student('人民路小学', '小红', 10)
student1.print_name()
#输出:小红
my name is 小红