第8章 类
本章学习目标:
- 熟练掌握类的设计和使用
- 深入了解类和对象、面向过程和面向对象的方法
- 掌握类的属性、类的方法、构造函数和析构函数、可变对象和不可变对象
- 理解运算符的重载
8.1 类的定义与使用
1、面向过程的程序设计方法:将数据与数据操作相独立,其中的数据操作通过函数或代码块来描述
2、面向对象程序设计方法:将数据与操作封装为一个混合整体——类,通过类进行数据的整体操作并且可以保证数据的完整性和一致性。
在面向对象编程中,编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。
==类的定义:==
类:是抽象的,具有相似特征和行为的事物的集合统称为类
对象:是具体的,是根据类创建的,一个类可以对应多个对象
每一个对象都是以某一个类的实例,类是生成对象的模板。
类和对象的关系
- 具有相似特征和行为的事物的集合统称为类
- 对象是根据类创建的,一个类可以对应多个对象
- 类是抽象的,而对象是具体的
- 每一个对象都是某一个类的实例
- 类是生成对象的模板
类是由3部分组成的:
- 类的名称:类名,比如Cartoon_sheep
- 类的属性:一组数据,比如姓名
- 类的方法:允许进行操作的方法,比如说话
类的使用:
- 类是在执行class语句时创建的,而对象是在调用类的时候创建的
- 每调用一次类,就创建一个对象。
- 类只有一个,而对象可以有多个。
- 类和每个对象都分别拥有自己的内存空间,在各自的内存空间存属于自己的数据。
使用class关键字来声明一个类,基本格式如下:
class类名:
类的属性
类的方法
class类名:
赋值语句
赋值语句
……
def语句定义函数
def语句定义函数
……
class Cartoon_sheep:
race='sheep' # 类属性
def eat(self): # 方法
print("我正在享受美食!")
类名的首字母一般要大写
在类中,用赋值语句创建类属性,用def定义函数
类属性是在类中方法之外定义的
根据类创建对象的语法格式如下:
对象名=类名()
a=Cartoon_sheep()
对象要调用类的属性和方法格式如下:
对象名.属性
a.race
对象名.方法()
a.eat()
思考eg8_1_1下面注释行的语句如何写:
class Cartoon_sheep:
race='sheep'
def eat(self,food):
print("我正在享受美食!",food)
def speak(self):
# 打印race属性
# 创建一个对象sheep1
# 调用speak方法
答:
class Cartoon_sheep:
race='sheep'
def eat(self,food):
print("我正在享受美食!",food)
def speak(self):
print(self.race) # 打印race属性
sheep1 = Cartoon_sheep() # 创建一个对象sheep1
sheep1.speak() # 调用speak方法
类的方法定义与普通函数的差别
- 类的方法的第一个参数都是self,self代表将来要创建的对象本身
- 在类的方法里,访问类的实例属性时,需要以self为前缀;
- 类的方法是通过对象来调用,即object.method()
思考8_1:定义鸟类Bird,鸟类的共同属性feather = True和reproduction = 'egg'。
该类有一个方法移动move(),该方法执行print('飞飞飞飞')
假设养了一只鹦鹉交spring,它是Bird的一个对象,输出鹦鹉的两个属性,并调用move方法
class Bird():
feather = True
reproduction ='egg'
def __init__(self,name):
self.name = name
def move(self):
print("飞飞飞飞")
bird = Bird('spring')
print(bird.feather)
print(bird.reproduction)
bird.move()
程序结果:
True
egg
飞飞飞飞
在Python中,所有的数据(包括数字和字符串)都是对象,同一类型的对象都有相同的类型。
内置函数isinstance()
来测试一个对象是否为某个类的实例或type()来获取关于对象的类型信息。
>>>type(sheep1)
>>>isinstance(sheep1,Cartoon_sheep)
True
8.2 构造函数
__init__
, 称为构造函数或初始化方法,用来为属性设置初值,在建立对象时自动执行。
当创建对象的时候,系统会自动调用构造方法,从而实现为实例属性设置初值。
如果用户未设计构造函数,Python将提供一个默认的构造函数。
[例]
class Cartoon_sheep:
race='sheep'
def __init__(self,namevalue,sexvalue,birthdayvalue):
self.name=namevalue
self.sex=sexvalue
self.birthday=birthdayvalue
self_energy=0
def eat(self):
print("我正在享受美食!")
def speak(self):
print(self.name,self.birthday)
sheep1 = Cartoon_sheep('喜洋洋','男','羊历3505年5月25日') # 在对象被建立之后,self被用来指向对象。
print(sheep1.name,sheep1.sex,sheep1.birthday)
# sheep1对象,sheep1.name="喜洋洋",sheep1.sex='男',sheep1.birthday='羊历3505年5月25日'
sheep1.speak()
程序结果:
喜洋洋 男 羊历3505年5月25日
喜洋洋 羊历3505年5月25日
思考si8_2: 定义Rectangle类表示矩形。该类有两个属性width和height,均在构造函数中创建,定义方法getArea和getPerimeter计算矩形的面积和周长。
class Rectangle():
def __init__(self,width,height):
self.width = width
self.height = height
def getArea(self):
a = (int(self.width))*(int(self.height))
return a
def getPerimeter(self):
b = (int(self.width))+(int(self.height))
B = 2*b
return B
t1 = Rectangle(15,6)
print(t1.getArea())
print(t1.getPerimeter())
程序结果:
90
42
class 类名:
属性=值
def __init__(self,形参,形参...):
def 方法(self,形参):
self.属性
对象=类名(形参1,形参2,...)
对象.属性
对象.方法(形参)
8.3 类的属性
属性有两种:类属性和对象属性
- 类属性:是该类所有对象共享,不属于任何一个对象。它在类方法之外定义,一般通过
类.属性
访问 - 对象属性通过
对象名.属性
通过对象名.属性
访问,一般在构造函数__init__
中进行初始化的,当然也可以在其他成员方法中定义。同一个类的不同对象的属性之间互不影响。
class Cartoon_sheep:
race='sheep' # 类属性
energy=1 # 类属性
def __init__(self,namevalue,sexvalue,birthdayvalue):
self.name=namevalue # 实例属性
self.sex=sexvalue # 实例属性
self.birthday=birthdayvalue # 实例属性
def study(self):
Cartoon_sheep.energy=Cartoon_sheep.energy+1 # 类属性
sheep1 = Cartoon_sheep('喜洋洋','男','羊历3505年5月25日')
sheep1.study()
print(sheep1.energy, Cartoon_sheep.energy) # 此处对sheep1的energy进行赋值,它是对象属性,自身本身energy 后续不会改变
程序结果:
2 2
修改和增加属性
- 对于类或对象而言,对属性进行赋值,修改该属性;当给不存在的属性赋值时,Python为其创建属性。
对象名.新的属性名 = 值
类.新的属性名 = 值
class Cartoon_sheep:
race='sheep'
def __init__(self,namevalue,sexvalue,birthdayvalue):
self.name = namevalue
self.sex = sexvalue
self.birthday = birthdayvalue
def study(self):
Cartoon_sheep.energy=Cartoon_sheep.energy + 1
sheep1 = Cartoon_sheep('喜洋洋','男','羊历3505年5月25日')
Cartoon_sheep.energy=0 # 创建类属性energy
sheep1.study()
print(sheep1.energy) # 此处对sheep1的energy没有进行赋值操作 它访问的是类属性,后续会改变
程序结果为:
1
- 若要修改类属性的值,必须对
类.属性
进行赋值 - 若是通过
对象.属性
,会产生一个同名的对象属性,这种方式修改的是对象属性,这种方式修改的是对象属性,不会影响到类属性,并且之后如果通过对象.属性
访问时,对象.属性
会强制屏蔽掉类属性,即访问的是对象.属性
, 除非del 对象.属性
class Cartoon_sheep:
race='sheep'
energy=1
def __init__(self,namevalue,sexvalue,birthdayvalue):
self.name=namevalue
self.sex=sexvalue
self.brithday=birthdayvalue
def study(self):
Cartoon_sheep.energy=Cartoon_sheep.energy+1 # 内属性
self.energy=self.energy+1 # 对象属性 创建了对象属性 self.energy+1中的self.energy为上述Cartoon_sheep.energy=2
sheep1 = Cartoon_sheep('喜羊羊','男','羊历3505年5月25日') # Cartoon_sheep.energy=1
sheep1.study() # Cartoon_sheep.energy=1+1=2; self.energy=Cartoon_sheep.energy=2 self.energy = self.energy+1=2+1=3
print(sheep1.energy,Cartoon_sheep.energy) # 3 2
程序结果:
3 2
思考8_3:下面代码的运行结果是什么?
class Testclass:
data=100 # 类属性
def setpdata(self,value):
self.pdata=value
def showpdata(self):
print("self.pdata=",self.pdata)
x = Testclass()
x.setpdata("与时俱进")
x.showpdata()
y = Testclass()
y.setpdata("勇于创新")
y.showpdata()
print(y.data)
程序运行结果:
self.pdata= 与时俱进
self.pdata= 勇于创新
100
思考8_4: 下面代码的运行结果是什么?
class Testclass:
data= "有信念、有梦想" # 类属性
def setdata(self,value):
self.data=value # 创建对象属性
def showdata(self):
print(self.data) # 打印对象属性
x=Testclass()
x.data="勇于创新"
y=Testclass()
Testclass.data="有奋斗、有奉献" # 更改了类属性
print(x.data) # 对象属性
print(y.data) # 没有对y.data赋值 没有创建对象属性,还是修改类属性
print(Testclass.data)
程序运行结果:
勇于创新
有奋斗、有奉献
有奋斗、有奉献
思考8_5:下面代码有1处出错,如何修改
class Rectangle:
def __init__(self,w,h):
self.width=w
self.height=h
def getArea(self):
return self.width*self.height
def getPerimeter(self):
return (self.width+self.height)*2
t1=Rectangle(2,3)
print(Rectangle.width) # 内属性
答:
print(t1.width)
类的属性分为:公有属性和私有属性
公有属性:可以在类的外部方位,它是类与用户之间交流的接口。用户可以通过公有变量向类中传递数据,也可以通过公有变量获取类中的数据。
私有属性:以__
(两个下划线)开头,不能在类的外部被使用或直接访问。在类内部的方法中使用时的格式为self.__私有属性
Python使用下划线作为变量前缀和后缀来指定特殊变量,规则如下:
__xxx__
:表示系统定义名字
__xxx
:表示类中的私有变量名
私有属性在类外面虽然可以通过如下形式访问,但不建议这样访问:
对象名._类名__私有属性
class Cartoon_sheep:
race = 'sheep' # 类属性
def __init__(self,namevalue,sexvalue,birthdayvalue):
self.name=namevalue
self.sex=sexvalue
self.__birthday = birthdayvalue ## 私有对象属性
self.__energy = 0 ## 私有对象属性
def study(self):
self.__energy = self.__energy+3
return self.__energy
sheep1 = Cartoon_sheep('喜羊羊','男','羊历3505年5月25日')
print(sheep1.study()) # self.__energy = self.__energy+3=0+3=3
print(sheep1.__energy) # print(sheep1.__energy) AttributeError: 'Cartoon_sheep' object has no attribute '__energy' 出错,私有属性不能在类外部访问
print(sheep1._Cartoon_sheep__energy) # 私有属性在类外面可以通过这种形式访问但不建议
程序结果:
3
3
==8.4 类的方法==
- 类中定义的方法都以self作为第一个参数,这个参数表示当前是哪一个对象要执行类的方法,这个实参由Python隐含地传递给self
- 当对象调用方法时,Python解释器会把对象自身作为第1个参数传给self,开发者只需传递后面的参数就可以了
类的方法分类:
- 公有方法:
对象名.公有方法(<实参>)
- 在类的内部,使用def可为类定义一个方法
- 与一般函数定义不同,类方法必须包含参数self,且为第一个参数
- self在Python里不是关键字,self代表当前对象调用方式:
对象名.公有方法(<实参>)
- 如果在外部通过类名调用属于对象的公有方法,需要显式为该方法的self参数传递一个对象名,用来明确指定访问哪个对象。
- 私有方法:只能在属于对象的方法中通过self调用,不能像公有方法一样通过对象名调用。
- 静态方法:使用修饰器@staticmethod来标识
- 类方法:使用修饰器@classmethod来标识
思考8_6:
class Cartoon_sheep:
race='sheep'
def __init__(self,namevalue,sexvalue,birthdayvalue):
self.name=namevalue
self.sex=sexvalue
self.__birthday=birthdayvalue
self._energy=0
def eat(self,food):
print("我正在吃",food)
sheep1= Cartoon_sheep('喜羊羊','男','羊历3505年5月25日')
下面哪些调用是正确的?
a) sheep1.eat('草') # sheep1-->self 草-->food
b) sheep1.eat(sheep1,"草")
c) Cartoon_sheep.eat('草')
d) Cartoon_sheep.eat(sheep1,'草') # sheep1-->self 草-->food
a) d)
私有方法
由两个下划线
__
开头,声明该方法为私有方法-
在类的内部方法调用时格式为:
self.__私有方法(<实参>)
-
一般不能在类的外部调用,但是与私有属性一样,在类外面访问方式如下
对象名._类名__私有方法
(不建议)
class Person:
pre_name="" # 类属性
def __init__(self,n,y,w,h): # 申明了构造函数
self.name=n # 公有属性
self.year=y # 公有属性
self.__weight=w # 私有属性
self.__height=h # 私有属性
def __getBMI(self): # 私有方法
bmi=1.0*self.__weight/self.__height**2 # 调用了私有属性
return bmi
def myBMI(self): # 公有方法
if 19<=self.__getBMI()<25: # 调用私有方法
print("身体质量指数正常")
else:
print("身体质量指数不正常")
pb=Person("Rose",1995,60,1.65)
pb.myBMI()
运行结果:
身体质量指数正常
思考8_7:下面Site的定义共有4处错,如何修改
class Site:
__number=0 # 私有类属性
def __init__(namevalue,urlvalue):
self.name=namevalue
self.__url=urlvalue # 私有对象属性
self.number=self.number+1 # 由下文可知应为私有属性
def printme(self):
__privateme(self) # 利用类属性的方法错误
print('name:',self.name)
print('url:',self.url) # 利用私有对象的方法错误
def __privateme(self): # 私有方法
print(Site.__number)
wz1 = Site("福建农林大学","wwww.fafu.edu.cn")
wz1.printme()
wz2 = Site("学习强国","www.xuexi.cn")
wz2.printme()
修改
class Site:
__number=0
def __init__(self,namevalue,urlvalue):
self.name=namevalue
self.__url=urlvalue
Site.__number=Site.__number+1 # 1
def printme(self):
Site.__privateme(self) # 2
print('name:',self.name)
print('url:',self.__url) # 3
def __privateme(self):
print(Site.__number)
wz1 = Site("福建农林大学","wwww.fafu.edu.cn")
wz1.printme()
wz2 = Site("学习强国","www.xuexi.cn")
wz2.printme()
程序结果:
1
name: 福建农林大学
url: wwww.fafu.edu.cn
2
name: 学习强国
url: www.xuexi.cn
静态方法:
- 使用修饰器@staticmethod
class 类名:
@staticmethod
def 静态方法名():
方法
静态方法第一个参数不需要是self参数
在静态方法中无法访问对象属性,只能访问属于类属性
静态方法可以通过类名和对象名调用;但是不属于任何实例,不会绑定到任何实例,当然也不依赖于任何实例的状态。
#eg8_1_7
class Cartoon_sheep:
race='sheep'
def __init__(self,na,se,bi):
self.name=na
self.sex=se
self.__birthday=bi
self._energy=0
@staticmethod
def speak():
print(Cartoon_sheep.race)
sheep1=Cartoon_sheep('喜羊羊','男','羊历3505年')
sheep1.speak()
Cartoon_sheep.speak()
程序结果:
sheep
sheep
思考8_8:调试下面代码,使之运行结果输出Flora:
class Person:
pre_name=""
def __init__(self,n,y,w,h):
self.pre_name=n
self.year=y
self.__weight=w
self.__height=h
@staticmethod
def setpre_name(self,n): # 不需要self这个参数
Person.pre_name=n
print(Person.pre_name)
a=Person("Flora",2020,150,180)
a.setpre_name("Flora")
调试:
class Person:
pre_name=""
def __init__(self,n,y,w,h):
self.pre_name=n
self.year=y
self.__weight=w
self.__height=h
@staticmethod
def setpre_name(n):
Person.pre_name=n
print(Person.pre_name)
a=Person("Flora",2020,150,180)
a.setpre_name("Flora")
类方法
类方法跟静态方法相似,通过类名和对象名调用
但不能直接访问对象属性,只能访问属于类属性
类方法一般以
cls
作为类方法的第一个参数表示该类自身,在调用类方法时不需要为该参数传递值。-
使用装饰符@classmethod定义类方法
class 类名: @classmethod def 类方法名(): 方法体
#eg8_1_8
class Cartoon_sheep:
race='sheep'
def __init__(self,na,se,bi):
self.name=na
self.sex=se
self.__birthday=bi
@classmethod
def speak(cls):
print(cls.race)
sheep1=Cartoon_sheep('喜羊羊','男','羊历3505年5月25日')
sheep1.speak()
Cartoon_sheep.speak() # 传递的是类名
程序结果:
sheep
sheep
思考si8_9:调试下面代码 输出 畲族 李四 2000 2:
class Person:
race="汉族"
def __init__(self,n,y):
self.name=n
self.year=y
self.number=1
@classmethod
def setPerson(n,y):
p=cls(n,y)
cls.race='畲族'
cls.number=cls.number+1
return p
pb=Person("张三",1995)
p1=Person.setPerson("李四",2000)
print(p1.race,p1.name,p1.year,p1.number)
调试:
class Person:
race="汉族"
def __init__(self,n,y):
self.name=n
self.year=y
self.number=1
@classmethod
def setPerson(cls,n,y):
p=cls(n,y)
cls.race='畲族'
p.number=p.number+1
return p
pb=Person("张三",1995)
p1=Person.setPerson("李四",2000)
print(p1.race,p1.name,p1.year,p1.number)
结果:
畲族 李四 2000 2
8.5析构方法
- Python中类的析构方法时
__del__
,用来释放对象占用的资源 - 如果用户未提供析构方法,Python将提供一个默认的析构方法。
- 析构方法在对象就要被垃圾回收之前调用,但发生调用的具体时间是不可知的,所以建议大家尽力避免使用
__del__
方法。
#eg8_3.py
class Pizza:
def __init__(self,d):
self.diameter=d
print("直径为")
def __del__(self):
print("直径为",self.diameter,"的Pizza吃光了")
pp1=Pizza(8)
pp2=Pizza(10)
del pp1 # 删除对象 所以可以触发 # del pp1.diameter 这是属性的删除 不会触发
del pp2 # del 整个实例删除时才会触发
8.6 Property内置函数
property()函数的作用是在新式类中返回属性值。需要限制对象属性的设置和获取。比如用户年龄为只读,或者在设置用户年龄的时候有范围限制。这时就可以使用property工具,它把方法包装成属性,让方法以属性的形式被访问和调用。
property内置函数格式如下:
x=property([fget[,fset[,fdel[,doc]]]])
- Fget -- 获取属性值的方法
- fset -- 设置属性值的方法
- fdel -- 删除属性值方法
- doc -- 属性描述信息
class Test:
def __init__(self,value):
self.__value=value
def __get(self):
return self.__value+1 # 3+1=4
def __set(self,v):
self.__value=v+2 # 3+2=5
def __delmyval(self):
print('删除属性value')
del self.__value
myval = property(__get,__set,__delmyval)
t=Test(3)
print(t.myval) # get ->4 获取属性值的方法
t.myval=3 # __set =>5 设置属性值的方法
print(t.myval) # 5+1 => get 获取属性值的方法
del t.myval # 删除属性值的方法
结果:
4
6
删除属性value
eg8_5:Rectangle类中,定义mywid和myhig属性,这两个属性赋值时要大于0,否则输出有误。
class Rectangle:
def __init__(self,w,h):
self._width=w
self._height=h
def getwidth(self):
return self._width
def setwidth(self,w):
if w>0:
self._width=w
else:
print("宽度width有误")
mywid = property(getwidth,setwidth)
def getheight(self):
return self._height
def setheight(self,h):
if h>0:
self_height=h
else:
print("高度height有误")
myhig= property(getheight,setheight)
def getArea(self):
return self.mywid*self.myhig
def getPerimeter(self):
return(self.mywid+self.myhig)*2
t2 = Rectangle(25,16)
print("矩阵t2的宽:",t2.mywid,",高:",t2.myhig)
print("矩阵t2的面积:",t2.getArea())
print("矩阵t2的周长:",t2.getPerimeter())
t2.mywid=8
print("矩阵t2新的宽:",t2.mywid)
t2.mywid=-8
print("矩阵t2新的宽:",t2.mywid)
程序结果:
矩阵t2的宽: 25 ,高: 16
矩阵t2的面积: 400
矩阵t2的周长: 82
矩阵t2新的宽: 8
宽度width有误
矩阵t2新的宽: 8
思考8_10:通过value属性来修改Test类的私有属性,若对value赋值时,值只能是[0,10],否则输出"请赋值0~10"。请根据题目要求补充代码。
class Test:
def __init__(self):
self.__size=0
def getsize(self):#返回私有变量__size的值
#####################
return self.__size
#####################
def setsize(self,v):#若v在[0,10]间,__size属性赋值为v
###############否则输出"请赋值0~10".
if v in range(0,11):
self.__size=v
else:
print("请赋值0~10")
#################
value=property(getsize,setsize )##补充完整property函数
a=Test()
a.value=11
程序结果:
请赋值0~10
==8.6 property函数和@property装饰器==
@property装饰器
@property
语法提供了比property()
函数更简洁的直观写法。
- 被
@property
装饰的方法时获取属性值的方法,被装饰方法的名字会被用做属性名 - 被
@属性名.setter
装饰的方法是设置属性值的方法。 - 被
@属性名.deleter
装饰的方法是删除属性值的方法。
class Test:
def __init__(self,value):
self.__value=value
@property
def myval(self):
return self.__value+1
@myval.setter
def myval(self,v):
self.__value = v+2
@myval.deleter
def myval(self):
print("删除属性value")
del self.__value
t= Test(3)
print(t.myval)
t.myval=3
print(t.myval)
del t.myval
思考8_10:修改下面代码。采用@property装饰器来定义Test类。通过value属性来修改Test类的私有属性。
class Test:
def __init__(self):
self.__size=0
def getsize(self):
return self.__size
def setsize(self,v):
if v in range(0,11):
self.__size=v
else:
print("请赋值0~10")
value=property(getsize,setsize )
a=Test()
a.value=11 # 设置属性
程序结果:
请赋值0~10
解:
class Test:
def __init__(self):
self.__size=0
@property
def getsize(self):
return self.__size
@getsize.setter
def getsize(self,v):
if v in range(0,11):
self.__size=v
else:
print("请赋值0~10")
a=Test()
a.getsize=11
程序结果:
请赋值0~10
==8.7 继承==**
- 继承是用来实现代码复用和设计复用的机制。设计一个新类时,如果继承一个己有的设计良好的类然后进行二次开发,无疑会大幅度减少开发工作量。
- 在继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类
python程序中,继承使用如下语法格式标注:
class 子类名(父类名):
假设有一个类为A,A派生出了子类B,示例如下:
class B(A):
class A(object): # 默认是继承自object的
例:
class Dog:
food="骨头"
def __init__(self,n,p):
self.species=n
self.__price = p
def shout(self):
print("汪汪")
class Spotty_dog(Dog):
pass
a=Spotty_dog("斑点狗",2000)
a.shout()
程序结果:
汪汪
- 在Python语言中,object类是所有类的最终父类,所有类最顶层的根都是object类。
- 在程序中创建一个类时,除非明确指定父类,否侧默认从python的根类object继承。
- 子类自动继承父类中的公共属性和方法
- 子类不能继承父类的私有属性,也无法在子类中访问父类的私有属性。
- 子类可以增加新的属性
- 父类与子类如果同时定义了名称相同的属性名称,父类中的属性在子类中将被覆盖。
class Dog:
food="骨头"
def __init__(self,n,p):
self.species=n
self.__price = p
def shout(self):
print("汪汪")
class Spotty_dog(Dog):
color="白色" #增加新的属性
food="狗粮"
a=Spotty_dog("斑点狗",2000)
a.shout()
print(a.food,a.color)
print(a.__price) # 出错 无法在子类中访问父类的私有属性
class Dog:
food="骨头"
def __init__(self,n,p):
self.species=n
self.__price = p
def shout(self):
print("汪汪")
class Spotty_dog(Dog):
def __init__(self,h): # 重新写属性
self.color="白色" #增加新的属性
self.food="狗粮"
a=Spotty_dog(200)
print(a.color)
b=Spotty_dog('斑点狗',200) # 重写了属性 不会调用父类的属性
print(a.species)
程序结果:
白色
Traceback (most recent call last):
File "/Users/eve/Desktop/PYTHON/SchoolCourse/第8章类/代码/2020-06-11/eg8_7_2.py", line 15, in
b=Spotty_dog('斑点狗',200)
TypeError: __init__() takes 2 positional arguments but 3 were given
思考8-12:子类Shark,继承Fish类;产生Shark对象时,能初始化其hungry属性为True,请写Shark类代码。
import random
class Fish:
def __init__(self):
self.x=random.randint(0,10)
self.y=random.randint(0,10)
def move(self):
self.x-=1
print("我的位置是",self.x,self.y)
答:
import random
class Fish:
def __init__(self):
self.x=random.randint(0,10)
self.y=random.randint(0,10)
def move(self):
self.x-=1
print("我的位置是",self.x,self.y)
class Shark(Fish):
def __init__(self):
self.hungry=True
a=Shark()
print(a.hungry)
==super()函数==
若Shark在产生对象时,除了能有自己的hungry属性,也想具有Fish类的x和y属性,那该如何操作?
==super()
函数==:能够自动找到基类的方法,而且还传递了self参数。调用方式:
super().方法名称(参数)
如super().__init__()
可以调用父类中的构造函数,这样子类也具有父类中构造函数里面的属性。
改进后的代码:
import random as ran
class Fish:
def __init__(self):
self.x=ran.randint(0,10)
self.y=ran.randint(0,10)
def move(self):
self.x-=1
print("我的位置是{},{}".format(self.x,self.y))
class Shark(Fish):
def __init__(self):
self.hungry=True
super().__init__()
s=Shark()
s.move()
程序结果:
我的位置是0,5
思考8_14:下面代码的运行结果是什么?
class Foo(object):
def __init__(self, a, b):
self.a = a
self.b = b
class Bar(Foo):
def __init__(self, a, c):
super().__init__(a,"有担当") # 创建了a,b属性
self.c = c
n = Bar("有理想","有本领")
print (n.a)
print (n.b)
print (n.c)
程序运行结果:
有理想
有担当
有本领
思考8-15:下面红色代码哪些是错误的
class Product( ):
id = 0
def __init__(self,name,price):
Product.id=Product.id+1
self.name=name
self.__price=price
def getPrice(self):
return self.__price
def __setPrice(self,value):
self.__price=value
class MobilePhone(Product):
def __init__(self,name,price,standard):
####### 一下错误的地方有哪些
super().__init__(name,price)
self.netstandard=standard
print(self.name)
print(self.id)
print(self.__price)
print(self.getPrice())
print(super().__setPrice())
#######
class Product( ):
id = 0
def __init__(self,name,price):
Product.id=Product.id+1
self.name=name
self.__price=price
def getPrice(self):
return self.__price # 返回数值
def __setPrice(self,value):
self.__price=value
class MobilePhone(Product):
def __init__(self,name,price,standard):
super().__init__(name,price)
self.netstandard=standard
print(self.name)
print(self.id)
print(self.__price) # 错误 在类的外面
print(self.getPrice())
print(super().__setPrice()) # 错误 私有方法在外面都不能访问它
a=MobilePhone("iphone",2000,"large")
程序运行
iphone
1
Traceback (most recent call last):
File "/Users/eve/Desktop/PYTHON/SchoolCourse/第8章类/代码/2020-06-11/si8_15.py", line 21, in
a=MobilePhone("iphone",2000,"large")
File "/Users/eve/Desktop/PYTHON/SchoolCourse/第8章类/代码/2020-06-11/si8_15.py", line 17, in __init__
print(self.__price) # 错误
AttributeError: 'MobilePhone' object has no attribute '_MobilePhone__price'
- 子类继承父类中的非私有属性和非私有方法
- 子类不能继承父类的私有属性和私有方法,也无法在子类中访问父亲的私有属性和私有方法。
- 子类可添加自己的属性和方法
- 子类可重新定义继承父类的方法
- super()函数可以调用父类的公有属性和方法
Python支持多继承,多继承就是子类拥有多个父类,并且具有它们共同的特征,即子类继承了父类公有的方法和属性
多继承
多继承可以看做是单继承的扩展,语法格式如下:
class 子类名(父类1,父类2,...):
父类里的方法名相同,默认从左到右调用括号里的父类
class Person:
def __init__(self,myhair,myage):
self.hair=myhair
self.hand=2
self.__age=myage
def move(self):
print('走路')
class Fish:
tail=1
def move(self):
print('游泳')
class Mermaid(Person,Fish):
pass
a=Mermaid("长头发",20)
a.move()
print(a.tail)
程序结果:
走路
1
#si8_15.py:下面代码的运行结果是什么?
class Product():
def testClassicalClass(self):
print('执行Product类')
class Computer(Product):
def testMethod(self):
print('执行Computer类')
class MobilePhone(Product):
def testClassicalClass(self):
print('执行MobilePhone类')
class SmartMobile (Computer,MobilePhone):
def testMethod(self):
print('执行SmartMobile类')
s = SmartMobile() # Computer,MobilePhone
s.testClassicalClass() # Computer->MobilePhone->testClassicalClass->"执行MobilePhone类"
s.testMethod() # 父类与子类如果同时定义了名称相同的属性名称,父类中的属性在子类中将被覆盖。
程序结果:
执行MobilePhone类
执行SmartMobile类
==8.8 多态==
多态是指基类(父类)的同一个方法在不同派生类(子类)对象中具有不同的表现和行为。派生类继承了基类行为和属性之后,还会增加某些特定的行为和属性,同时还可能会对继承来的某些行为进行一定的改变,这都是多态的表现形式。
class A(object):
def test(self):
print("--A--test")
# A 类
class B(A):
def test(self):
print("--B--test")
# B 类
def func(temp):
temp.test
a=A()
b=B()
func(a)
func(b)
# a、b的对象两次调用func函数结果不一样
思考8-17,下面程序的运行结果是什么?
class Animal(object):
def show(self):
print('I am an animal.')
class Cat(Animal):
def show(self):
print('I am a cat.')
class Dog(Animal):
def show(self):
print('I am a dog.')
class Test(Animal):
pass
x = [item() for item in (Animal, Cat, Dog, Test)] # x是个列表
for item in x:
item.show()
结果:
I am an animal.
I am a cat.
I am a dog.
I am an animal.
多态简单点讲就是对父类方法进行重写。
抽象类中定义子类应该实现的一组抽象方法。子类继承抽象类时,不能通过实例化使用其抽象方法,必须实现该方法。
通过import abc模块来实现。
import abc
class Parent(abc.ABC):
@abc.abstractmethod
def login(self): pass # 抽象方法不用去定义它
class Son(Parent):
def __init__(self,name,passwd): # 初始化下它的name,password
self.name = name
self.password = passwd
def login(self):
if self.name == "fangyan" and self.password == "123":
print("登录成功")
else:
print("登录失败")
a=Son('fangyan','123')
a.login()
==8.9 运算符的重载==
运算符重载,对某种方法进行冲写,如:
>>> 2+3
5
#这边的加号两边是两个数值,计算二者的和
>>> "ab"+"cd"
'abcd'
#这边的加法运算在字符串类中被赋予了新的功能,这就是运算符重载
为运算符定义方法被称为运算符的重载,每个运算符都对应着一个函数,因此重载运算符就是实现函数。
例如:“+”运算符是类里提供的__add__
这个函数(并不是私有方法),当调用”+“实现加法运算的时候,实际上是调用了__add__
方法
>>> "ab".__add__("cd")
'abcd'
# __add__方法重载了运算符“+”
# 即m.__add__(n)与m+n是一致的
常用的运算符与函数的对应关系
运算符 | 关系型运算符 |
---|---|
+ | __add__(self,other) |
- | __sub__(self,other) |
* | __mul__(self,other) |
/ | __truediv__(self,other) |
// | __floordiv__(self,other) |
% | __mod__(self,other) |
** | __pow__(self,other) |
< | __lt__(self,other) |
<= | __e__(self,other) |
== | __eq__(self,other) |
> | __gt__(self,other) |
>= | __ge__(self,other) |
!= | __ne__(self,other) |
[例] 定义Number类,使得该类的对象x能与数值进行加法运算。如x=Number(5);x+2结果为7
#eg8_10
class Number:
def __init__(self,start): # start=5
self.data=start # self.data=5
def __add__(self,other): # _add_ +
return self.data+other # self.data+other = 5+2
x=Number(5)
print(x+2)
程序结果:
7
倘若想要实现x=Number()5,y=Number(7),print(x+y)
class Number:
def __init__(self,start): # start=5 start=7
self.data=start # x=self.data=5 y=self.data=7
def __add__(self,other):
return self.data+other.data # x.data+y.data=5+7
x=Number(5) # 传递给self
#print(x+2)
y=Number(7) # other的data
print(x+y) # x传给self,y传给other
程序结果:
12
#eg8_11
class Number:
def __init__(self,a): # a="ab" a="ab"
self.data=a # x.data="ab" y.data="ab"
def __lt__(self,other): # x=self y=other
return self.data
程序结果:
False
class Number:
def __init__(self,a):
self.data=a
def __lt__(self,other):
return self.data
程序结果:
True
常用的运算符与函数的对应关系
运算符 | 方法 | 说明 |
---|---|---|
[index] | __getitem__(self,index) |
按照索引取值 |
__setitem__() |
按照索引赋值 | |
in | __contains__(self,value) |
是否为成员 |
len | __len__(self) |
元素个数 |
str | __str__(self) |
与str()\print()对应,要求该方法必须返回str类型的数据 |
[例]:定义Number类,类的对象x可以进行分片和修改元素的操作。
如:x=Number([1,2,3,4,5]);x=[2:4]=[6]
class Number:
def __init__(self,a): # a=[1,2,3,4,5]
self.data=a[:] # 对a进行拷贝 self.data=[1,2,3,4,5]
def __getitem__(self,index): # 按照索引取值
return self.data[index] # x=[1,2,6,5]
def __setitem__(self,index,value):
self.data[index]=value # x.data[2:4]=6
x=Number([1,2,3,4,5]) # a=[1,2,3,4,5] self.data=[1,2,3,4,5]
x[2:4]=[6] # x中的3,4被替换成6了
print(x[:]) # x=[1,2,6,5]
程序结果:
[1, 2, 6, 5]
__str()__
重载时:必须返回str类型的数据。当运行str(对象)或是print(对象)时会触发该方法。
class test:
datal = 10
a=test()
t=str(a)
print(t)
程序结果:
<__main__.test object at 0x10ee26fd0>
class test:
datal = 10
def __str__(self):
return "Hi, I am test"
a=test()
t=str(a)
print(t)
程序结果:
Hi, I am test
思考8_18:下面程序的运行结果是什么?
class test:
data1=100
def __init__(self,n):
self.data2=n
def __str__(self):
return "data1={},data2={}".format(self.data1,self.data2)
a=test(20)
print(a) # print会触发 __str__()这个方法
程序结果:
data1=100,data2=20
思考8_19:补充完整Rectangle类,使得该类的对象可以进行如下运算:
1.a+b:求出两个矩形的面积和。
2.a==b
:判断两个面积是否相等;若a==b
,表达式的值为”相等“,否则为”不相等“。
3.print(a):打印出该矩形的面积
class Rectangle:###补充完整,使其对象能进行+,==,print操作
def __init__(self,w,h):
self.width=w
self.height=h
def getArea(self):
return self.width*self.height
############################
a=Rectangle(2,4)
print(a)
b=Rectangle(3,6)
print(a+b)
print(a==b)
答:
class Rectangle:###补充完整,使其对象能进行+,==,print操作
def __init__(self,w,h):
self.width=w
self.height=h
def getArea(self):
return self.width*self.height
def __add__(self, other):
return self.getArea()+other.getArea()
def __eq__(self, other):
if self.getArea()==other.getArea():
return "相等"
else:
return "不相等"
def __str__(self):
return "该矩形的面积为"+str(self.getArea())
############################
a=Rectangle(2,4)
print(a)
b=Rectangle(3,6)
print(a+b)
print(a==b)
程序结果:
该矩形的面积为8
26
不相等
思考8_19_1:
#8_19_1
class Rectangle:
def __init__(self,w,h):
self.width=w
self.height=h
def getArea(self):
return self.width*self.height
def __add__(self,other):
return "两矩形的面积和是:"+str(self.getArea()+other.getArea())
def __eq__(self,other):
if self.getArea()==other.getArea():
return "相等"
else:
return "不相等"
def __str__(self):
return "该矩形的面积是:"+str(self.getArea())
if __name__ == '__main__': # 当作主程序运行
print('I runs as main')
else:
print(__name__,'I runs as a module') # 当作模块导入另一文件运行
程序结果:
I runs as main
若代码是主程序,__name__
值为__main__
;若代码作为import模块,__name__
值为文件名
当被当作模块被导入其他文件运行
from si8_19_1 import Rectangle # 代码模块文件导入
a=Rectangle(2,4)
print(a)
b=Rectangle(3,6)
print(a+b)
print(a==b)
程序结果:
si8_19_1 I runs as a module
该矩形的面积是:8
两矩形的面积和是:26
不相等
补充:自定义异常类
- 有时候需要自定义一些异常类,系统无法识别自定义的异常类,只能在程序中使用raise抛出异常。
- raise唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是Exception的子类)
#eg8_13:
try: # try ... except 捕捉异常语句
raise NameError # raise抛出异常类的子类
except NameError:
print("An exception flew by!")
程序结果:
An exception flew by!
思考8_20:运行该程序时输入15或150的结果是什么?
def get_age():
age = int(input('请输入年龄(1-140):')) #15 150
if age in range(1,141): #15 is in 150 is not in
return age # 15
raise ValueError # 150 抛出异常
try:
age = get_age() # 调用这个函数
print('用户输入的年龄是:', age) #15
except ValueError:
print('用户输入的年龄不在1-140之间!') # 150
程序结果:
请输入年龄(1-140):15
用户输入的年龄是: 15
请输入年龄(1-140):150
用户输入的年龄不在1-140之间!
思考8_21:下面程序的运行结果是什么?
class MyError(Exception):
def __init__(self,value): # 4
self.value=value # 4
def __str__(self):
return self.value
try:
raise MyError(2*2)
except MyError as e :
print("An exception flew by!",e.value)
程序结果:
An exception flew by! 4
总结:
- 类的定义与使用
- 构造方法与析构方法
- 属性(公有与私有,类属性与实例属性)
- 类的方法(公有、私有、静态、类方法、抽象方法)
- property内置函数与修饰词
- 继承
- 多态、运算符的重载
实验
一、 实验目的和要求
(1)类的继承;(2)调用超类的构造方法;(3)运算符重载 (4)异常处理
- 【题目描述】
请打开shiyan8_1.py,补充完Vecter3的定义,进行运算符重载,使得该类能进行加法和乘法运算,并能打印。程序另存为学号_1.py。程序运行结果如下:
5,7,9
10,20,30
解:
##请补充完类Vecter3的定义
class Vecter3(object):
def __init__(self,x,y,z):
self.X=x
self.Y=y
self.Z=z
def __add__(self,other):
t=Vecter3(0,0,0)
t.X=self.X+other.X
t.Y=self.Y+other.Y
t.Z=self.Z+other.Z
return t
def __mul__(self, n):
t=Vecter3(0,0,0)
t.X=self.X*n
t.Y=self.Y*n
t.Z=self.Z*n
return t
def __str__(self):
return str(self.X)+","+str(self.Y)+","+str(self.Z)
##下面代码请不要修改
if __name__=='__main__':
v1=Vecter3(1,2,3)
v2=Vecter3(4,5,6)
print(v1+v2)
print(v1*10)
程序结果:
5,7,9
10,20,30
2.【题目描述】
以r+模式打开用户输入的文件。若文件不存在,系统会产生异常FileNotFoundError,请捕捉该异常类,然后输出“该文件不存在”。若文件存在,让用户输入字符串,保存到该文件中。当用户输入-1,结束输入。当用户输入的字符串长度大于10,要抛出InvalidError类。请捕捉InvalidError类并产生实例t,执行print(t)和fp.close()。请打开shiyan8_2.py文件,补充完代码,程序另存为学号_2.py。
- 当输入的文件不存在,程序运行结果如下所示:
输入文件名:ff
文件不存在
- 当输入的文件存在时,程序运行结果如下所示:
输入文件名:f1.txt
输入字符串:hello
输入字符串:You are welcome
输入的字符串太长,长度为15
解:
class InvalidError(BaseException):
def __init__(self,n):
super().__init__()
self.num=n
def __str__(self):
return "输入的字符串太长,长度为"+str(self.num)
####当文件不存在,要能捕捉FileNotFoundError(系统产生的异常类)
####当文件存在,用户输入数据保存到文件中
####若输入的字符串长度>10,抛出InvalidError(自定义的异常类)
####要能捕捉InvalidError
####当用户输入-1结束输入
try:
filename = input('输入文件名:')
fp = open(filename , "r+")
ch = input("输入字符串:")
while ch!='-1':
if len(ch)<=10:
fp.write(ch)
ch=input("输入字符串:")
else:
raise InvalidError(len(ch))
except FileNotFoundError:
print("文件不存在")
except InvalidError as t:
print(t)
fp.close()
程序结果:
输入文件名:ff
文件不存在
输入文件名:f1.txt
输入字符串:hello
输入字符串:You are welcome
输入的字符串太长,长度为15