跳转至

自定义类

点击这里,边看视频讲解,边学习以下内容

这节课内容理解上有一定的难度,如果大家一遍看不懂,不用沮丧,说明你是正常人的智商。 如果一遍就搞懂了,恭喜你,至少比白月黑羽聪明多了,我当年可是反复学习了好多遍才逐渐明白的。


什么是类

Python 中的一切对象都有各自的类型,比如

整数对象   的类型是   int
字符串对象 的类型是   str
列表对象   的类型是   list
元组对象   的类型是   tuple
字典对象   的类型是   dict

Python 的内置函数type可以查看对象的类型

>>> type(12)
<class 'int'>    # 整数类型
>>> type('12')
<class 'str'>    # 字符类型
>>> type([1,2])
<class 'list'>   # 列表类型
>>> type((1,2))
<class 'tuple'>  # 元组类型
>>> type({1:2})
<class 'dict'>   # 字典类型

我们掌握了这些内置的数据类型,通常就可以开发Python程序了。

但是当我们要开发的软件系统 更加复杂的时候,尤其是系统里面的对象 和现实世界的对象 存在对应关系的时候,如果只能用这些内置类型,就会感觉很不方便了。

比如,我们的程序要表示一个 奔驰汽车 这样的对象类型,属性有:品牌,国家,价格。

如果只用Python的内置类型,大家想想怎么表示 。

当然,我们可以定义一个字典类型的对象,比如

benzCar = {
    'brand'   : '奔驰',
    'country' : '德国',
    'price'   : 300000
}

如果这个汽车对象还需要有自己特定的行为,比如 按喇叭会发出嘟嘟的声音。那又该怎么定义呢?

有人说,可以定义一个函数对象作为它的属性,像这样

def  pressHorn():
    print('嘟嘟~~~~~~')

benzCar = {
    'brand'   : '奔驰',
    'country' : '德国',
    'price'   : 300000,
    'pressHorn' : pressHorn # 字典对象的值可以是一个函数对象
}

# 我可以这样执行它的行为
benzCar['pressHorn']()

似乎也可以。

但是这里 benzCar 更像是一个具体的对象,并不是一种 对象类型

而且 这个 benzCar 汽车的 行为的定义 ,要在外面定义一个函数, 然后benzCar字典的内部去引用它,这样也比较麻烦。



为了解决这样的普遍问题,Python语言可以让我们 自己定义对象类型

Python中自定义对象类型,就是 定义一个类 就是 类型 的意思。

比如 : 奔驰汽车, 可以这样定义

class BenzCar:    
    brand   = '奔驰'  # 品牌属性
    country = '德国'  # 产地属性

    @staticmethod
    def pressHorn(): 
        print('嘟嘟~~~~~~')

定义一个类 用关键字 class 后面加 类的名称。

类名的规范 和 变量命名规范一样。 通常我们会把类名 首字母大写, 这里定义的类名就是 BenzCar

下面定义的 brand, country 都是 BenzCar 类的 属性

这种属性被称为类属性

如果我们要得到属性的值可以这样用 类名.属性名 的方式,如下

print(BenzCar.brand) 

而 pressHorn 则是该类型的一个 方法。 请注意上面的 @staticmethod 的修饰, 说明这是该类的一个 静态方法

要调用执行该类的静态方法,像这样就可以了

BenzCar.pressHorn()

大家可以拷贝如下代码到一个文件中,执行一下看看

class BenzCar:    
    brand   = '奔驰'  
    country = '德国'  

    @staticmethod
    def pressHorn(): 
        print('嘟嘟~~~~~~')

BenzCar.pressHorn()

类的实例


点击这里,边看视频讲解,边学习以下内容

类和实例的关系

Python中 类 是 某种对象的类型。

比如 int 是 整数对象的类型, str是字符串对象的类型, list是 列表对象的类型。

我们把一个个具体的 对象称为 该类型的 实例

比如,我们可以说

数字对象 3 是 int 类型的的实例,具有int类型的特征

字符串对象 'abc' 是 str 类型的实例,具有str类型的特性(比如可以执行str的所有方法,比如 find, split等)

列表对象 [1,2,3] 是 list 类型的的实例,具有list类型的特性(比如可以执行list的所有方法,比如 reverse,append等)

同样的,我们自定义的类,也可以产生该类的实例对象。 每个实例对象就是该类的一个实例,具有该类的一切特征。

要产生一个类的实例对象,只需要 在类名后面加上括号,就可以了,就会返回一个该类的实例对象。

比如

car1 = BenzCar()

car1 变量就对应了一个 BenzCar 类型 的实例对象,具有 BenzCar 类的一切属性和方法。大家可以执行下面的代码试试。

class BenzCar:    
    brand   = '奔驰'  
    country = '德国'  

    @staticmethod
    def pressHorn(): 
        print('嘟嘟~~~~~~')

car1 = BenzCar()       
print(car1.brand) 
car1.pressHorn()

同样,我们也可以用 type 函数查看 car1 这个实例的类型,如下所示

>>> type(car1)
<class '__main__.BenzCar'>

说明 car1 是 __main__ 模块里面定义的 BenzCar 类型。


大家一定要搞清楚 类 和 实例 的关系。

比如 :

人 就是 一个 类, 而 关羽、张飞 就是 人 这个类的 具体实例。

狗 也是 一个 类, 而 你们家的阿黄 和 隔壁家的旺财 就是狗 这个类的 具体实例。。

Python中 定义一个类型 就是描述 这些类型的实例的 公共特征 。后面根据这个类创建的实例 都具有这个类的 特征,就是 具体什么 属性、方法。

实例属性和实例方法


点击这里,边看视频讲解,边学习以下内容

刚才我们定义的类里面的属性都是 类属性 ,里面的方法都是类的 静态方法

所有BenzCar类的实例对象,其 品牌名 brand ,对应的类属性应该是相同的。

就是说下面这样的两个实例

car1 = BenzCar()     
car2 = BenzCar()

car1 和 car2 的 brand属性 都是一样的 值, 都是字符串 '奔驰'

很好理解,因为品牌这样的属性 对于所有的 奔驰车都是一样的,都是 '奔驰'。

类属性 是类的共同特征属性。


但是有些属性,比如颜色、发动机编号 是每一辆奔驰车 都不同的。

所以,在我们定义的 类BenzCar 里面, 颜色、发动机编号 是 不应该 作为类属性的。

每个实例独有的属性,称之为 类的实例属性

实例属性通常是在类的 初始化方法 __init__ 里面定义的。

比如:

class BenzCar:    
    brand   = '奔驰'  
    country = '德国'  

    @staticmethod
    def pressHorn(): 
        print('嘟嘟~~~~~~')

    # 初始化方法, 注意前后各有两个下划线
    def __init__(self):
        self.color  =  'red'        # 颜色
        self.engineSN = '837873398' # 发动机编号

上面的初始化方法 __init__ ,就创建了两个实例属性 color 和 engineSN。

为什么 __init__ 方法 叫初始化方法呢?

解释器在执行 像下面这样的 实例化类对象 的代码时,

car1 = BenzCar()     

首先,解释器会 在内存中 创建一个该类 的 实例对象;

然后,解释器会查看这个类是否有 __init__方法,如果有,就会去调用它。

__init__ 是 创建好实例后 立即就要 执行 的方法,所以称之为初始化方法。


通常我们会在__init__方法里面 执行一些初始化的动作,主要就是创建该实例的 实例属性。

__init__ 方法的第一个参数是 self, 它 是干什么用的呢?

刚才说了, 解释器执行实例化代码,会先在内存中创建该类实例对象,然后调用类 的__init__方法。

调用 __init__方法时,就将实例对象 传递给 self参数。

self 参数变量 指向的 就是 实例对象 本身, 所以下面的代码就是创建该实例的属性color 和 engineSN 了

        self.color  =  'red'         # 颜色
        self.engineSN = '8378738398' # 发动机编号


类的静态方法要在方法定义 上面加上 @staticmethod 的修饰。

而 类的 实例方法 不需要任何修饰。

通常类的实例方法,都是要 访问类的实例属性的。 包括: 创建、修改、删除 类的实例属性。

因为 实例方法 就是要操作 实例独有的属性,否则不操作任何实例属性的话,就应该定义为 类方法。

比如 __init__ 初始化方法,就是一个实例方法,它通常要创建一些实例属性。

pressHorn 方法是类的静态方法, 静态方法是不能访问实例属性的



有时候,实例属性的取值,不是固定写在初始化方法的代码里面。

比如这里,每辆车的颜色、发动机号都是不同的,我们应该作为参数传进去。

所以修改代码为这样

class BenzCar:    
    brand   = '奔驰'  
    country = '德国'  

    @staticmethod
    def pressHorn(): 
        print('嘟嘟~~~~~~')

    def __init__(self,color,engineSN):
        self.color  =  color     # 颜色
        self.engineSN = engineSN # 发动机编号

这样我们在创建实例的时候,就可以根据需要指定不同的实例属性了,比如

car1 = BenzCar('白色','24503425527866')
car2 = BenzCar('黑色','34598423586877')
print(car1.color)
print(car2.color)
print(car1.engineSN)
print(car2.engineSN)

虽然定义的时候, __init__ 方法 有3个参数 : self,color,engineSN

但是我们这样调用 BenzCar() 实例化的时候, 只需要传入后面两个参数即可。

因为self 参数 需要传入实例对象本身,解释器会自动帮我们传入。

其它的 实例方法也是这样, 比如我们定义一个 修改车身颜色的方法 changeColor

class BenzCar:     
    brand   = '奔驰'  
    country = '德国'  

    @staticmethod
    def pressHorn(): 
        print('嘟嘟~~~~~~')

    def __init__(self,color,engineSN):
        self.color  =  color     # 颜色
        self.engineSN = engineSN # 发动机编号

    def changeColor(self,newColor):
        self.color = newColor

car1 = BenzCar('白色','24503425527866')       
car1.changeColor('黑色')

print (car1.color)

调用 changeColor方法的时候,只需要传入参数 newColor 对应新的颜色即可。

不需要我们传入self参数,self 参数是实例对象本身,解释器会自动帮我们传入。


注意: 如果你的实例属性名称 和 静态属性 重复了 ,通过类实例访问该属性,访问的是实例属性。通过类名访问该属性,访问的是类属性。

比如

class Car:
    brand = '奔驰'
    name = 'Car'

    def __init__(self):
        # 可以通过实例访问到类属性
        print(self.brand)

        # 定义实例属性和类属性重名
        self.name = 'benz car'

c1 = Car()

print(f'通过实例名访问name:{c1.name}')
print(f'通过类名  访问name:{Car.name}')

一旦创建了 和类属性同名的 实例属性,通过实例访问的就是实例属性了

参考 https://stackoverflow.com/questions/12949064/python-what-happens-when-class-attribute-instance-attribute-and-method-all-ha


类之间的关系

继承关系


点击这里,边看视频讲解,边学习以下内容

真实世界中,类型之间 可能存在 范围 包含关系。

比如: 这个类型 和 亚洲人 这个类型。

是包括了 亚洲人 的。 如果 某人 是一个 亚洲人,那么它必定是一个

这种关系,编程语言中称之为 继承关系

比如上面的例子, 亚洲人 这个类 就 继承 这个类。

通常我们把被继承的类称之为 父类 或者叫 基类

把继承类称之为 子类 或者 派生类

同样的,以车为例, 上面我们定义了奔驰车 这个类, 我们还可以定义两个 子类: 奔驰2016奔驰2018 对应两种不同款的奔驰车。

如下所示:

class BenzCar:    
    brand   = '奔驰'  
    country = '德国'  

    @staticmethod
    def pressHorn(): 
        print('嘟嘟~~~~~~')

    def __init__(self,color,engineSN):
        self.color  =  color  # 颜色
        self.engineSN = engineSN # 发动机编号

    def changeColor(self,newColor):
        self.color = newColor

class Benz2016(BenzCar):
    price   = 580000
    model   = 'Benz2016'   

class Benz2018(BenzCar):
    price   = 880000
    model   = 'Benz2018'    

大家可以发现定义子类的时候,必须指定它的父类是什么。

指定的方法就是在类名的后面的括号里写上父类的名字。


大家注意: 子类会自动拥有父类的一切属性和方法

为什么? 因为一个子类的实例对象 ,必定也是一个父类的实例对象。 当然需要拥有父类的一切属性和方法。

就像 一个亚洲人 当然 拥有一个 所应该具有的一切特性。

比如,执行下面的代码

car1 = Benz2016('red','234234545622')    
car2 = Benz2018('blue','111135545988')   

print (car1.brand)
print (car1.country)
car1.changeColor('black')

print (car2.brand)
print (car2.country)
car2.pressHorn()

输出结果如下

奔驰
德国
奔驰
德国
嘟嘟~~~~~~



一个子类在继承父类的一切特性的基础上,可以有自己的属性和方法。 比如:

class Benz2018(BenzCar):
    price   = 880000
    model   = 'Benz2018'     

    def __init__(self,color,engineSN,weight):
        # 先调用父类的初始化方法
        BenzCar.__init__(self,color,engineSN)
        self.weight = weight # 车的重量
        self.oilweight = 0  # 油的重量

    # 加油
    def fillOil(self, oilAdded):
        self.oilweight +=  oilAdded 
        self.weight    +=  oilAdded

这里 子类 Benz2018 ,新增了两个 类属性

价格 price 
型号 model


新增了两个实例属性

整车重量weight 
油的重量oilweight


新增了一个实例方法 fillOil , 对应 加油这个行为。

这个行为会导致 实例属性 weight 和 oilweight 变化,所以必须是 实例方法。

这样定义好了以后, 就可以创建该类的实例,并访问其新的方法和属性了。

car2 = Benz2018('blue','111135545988',1500)   
print (car2.oilweight)
print (car2.weight)
car2.fillOil(50) 
print (car2.oilweight)
print (car2.weight)


要特别注意的是, 子类的初始化方法里面,如果有一部分的初始化代码和父类的初始化相同(通常都是这样),需要显式的 调用父类的初始化方法 __init__

而且要传入相应的参数, 像上面那样,然后可以加上自己的特有的初始化代码。如下所示

def __init__(self,color,engineSN,weight):
    # 先调用父类的初始化方法
    BenzCar.__init__(self,color,engineSN)
    self.weight = weight 
    self.oilweight = 0  


如果子类 没有 自己的初始化方法,实例化子类对象时,解释器会自动调用父类初始化方法,如下

class Rect:
    def __init__(self):
        print('初始化 rect')

class Squre(Rect):
    pass

s = Squre()

运行结果,会打印出 初始化 rect


但是,如果子类 有自己 的初始化方法,实例化子类对象时,解释器就不会自动化调用父类的初始化方法,如下

class Rect:
    def __init__(self):
        print('初始化 rect')

class Square(Rect):
    def __init__(self):
        print('初始化 square')

s = Squre()

运行结果只会打印 初始化 square


调用父类的方法,除了直接用父类的名字 BenzCar, 还可以使用 函数 super()

像这样

def __init__(self,color,engineSN,weight):
    # 同样是调用父类的初始化方法
    super().__init__(color, engineSN)
    self.weight = weight 
    self.oilweight = 0  

这样使用的时候,方法参数中 不需要加上 self 参数。

使用super的好处之一就是:子类中调用父类的方法,不需要 显式指定 父类的名字。 代码的可维护性更好。

想象一下,如果BenzCar有很多子类,如果那一天BenzCar类改了名字,采用super这样的写法,就不需要修改子类的代码了。

另外一个更重要的好处是,在多重继承的情况,super能有效的保证继承链上的方法被正确调用到,感兴趣的朋友可以参考stack overflow上的阐述 , 和 这里的官方文档


注意 super不仅仅可以调用父类的初始化方法,也可以调用父类的其他方法。


一个子类,同时还可以是另一个类的父类。

比如 亚洲人 可以是 的子类, 同时可以是 中国人 的父类。

因为一个中国人,一定是一个亚洲人, 当然也一定是一个 人 。

同样的,上面的车的例子, 我们还可以定义 奔驰2018混合动力 作为 奔驰2018 的 子类。

定义的语法还是一样的

class Benz2018Hybrid(Benz2018):
    model = 'Benz2018Hybrid' 
    price = 980000

    def __init__(self,color,engineSN,weight):
        Benz2018.__init__(self,color,engineSN,weight)

同样,类 Benz2018Hybrid 也会拥有其父类 Benz2018 的一切属性和方法,自然也包括 父类的父类 BenzCar 的一切属性和方法

car2 = Benz2018Hybrid('blue','111135545988',1500)   
print (car2.oilweight)
print (car2.weight)
car2.fillOil(50) 
print (car2.oilweight)
print (car2.weight)



类的组合关系


点击这里,边看视频讲解,边学习以下内容

除了上面的继承关系, 类之间还有一种常见的组合关系。

所谓组合关系,就是一个类实例的属性里面包含另外一个类实例。

比如

class BenzCar:    
    brand   = '奔驰'  
    country = '德国'  


    def __init__(self,color,engineSN):
        self.color  =  color     # 颜色
        self.engineSN = engineSN # 发动机编号

这样的定义,类 BenzCar 中

brand 属性就是一个字符串对象 奔驰

country 属性就是一个字符串对象 德国

而该类的实例对象中,就包含了 两个属性 color 和 engineSN, 都是字符串对象

我们可以说 该类由 一些字符串对象 组合 而成。

甚至还可以包含 我们自己定义的类的实例,比如:

# 轮胎
class Tire:    
    def __init__(self,size,createDate):
        self.size  =  size  # 尺寸
        self.createDate = createDate # 出厂日期

class BenzCar:    
    brand   = '奔驰'  
    country = '德国'  

    def __init__(self,color,engineSN,tires):
        self.color  =  color  # 颜色
        self.engineSN = engineSN # 发动机编号
        self.tires   =  tires

# 创建4个轮胎实例对象
tires = [Tire(20,'20160808')  for i in range(4)]
car = BenzCar('red','234342342342566',tires)

上面的例子里,奔驰汽车对象就 包含 了4个轮胎 Tire 对象。

我们可以说奔驰汽车对象是由 4个轮胎对象 组合 而成,形成了对象的组合关系。

对象的 属性 变量 保存了 组合它的那些对象。


组合关系,可以通过上层对象的属性一步的访问到内部对象的属性

比如,我们可以通过 BenzCar 对象 访问其内部的轮胎对象

print(car.tires[0].size)

Python解释器对这个表达式 car.tires[0].size 是从左到右依次执行的,如下所示

car.tires   # BenzCar实例的tires属性,得到一个列表,里面是四个 Tire 实例对象

car.tires[0]   # 得到BenzCar实例的tires属性列表里面的第1个Tire 实例对象

car.tires[0].size  # 得到BenzCar实例的tires属性列表里面的第1个Tire 实例对象的size属性

您需要高效学习,找工作? 点击咨询 报名实战班

点击查看学员就业情况


课后练习

点击这里,去做练习


初学者 往往搞不明白 实例 的关系。

参考下面 我 和 实战班学员的 交流

https://www.bilibili.com/video/BV1VJ411v7zA?p=34


另外,前面的课程刚刚学过调试,当你开发的代码出现问题时,要会通过 断点调试的方式 解决问题,

参考下面 我们辅导 小班学员调试程序的 视频

https://www.bilibili.com/video/BV1GJ411q7io?p=14