2020-06-25

第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)异常处理

  1. 【题目描述】

请打开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。

  1. 当输入的文件不存在,程序运行结果如下所示:
输入文件名:ff
文件不存在
  1. 当输入的文件存在时,程序运行结果如下所示:
输入文件名: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

你可能感兴趣的:(2020-06-25)