自定义类

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的内置类型,大家想想怎么表示 。

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

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()

类的实例

类和实例的关系

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()

实例属性和实例方法

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

所有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___ 方法的第一个参数是 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('red','24503425527866')
car2 = BenzCar('blue','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('red','24503425527866')       
car1.changeColor('black')

print (car1.color)

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

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


类之间的关系

继承关系

真实世界中不同类型存在一种常见的 范围 包含关系。

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

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

这种关系,我们在面向对象的编程语言中称之为 继承关系

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

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

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

同样的,以车为例, 上面我们定义了奔驰车 这个类, 我们还可以定义两个 子类: 奔驰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  


除了直接用父类的名字 BenzCar, 另外一种使用父类的方法,就是使用 函数 super()

像这样

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

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



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

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

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

同样的,上面的车的例子, 我们还可以定义 奔驰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个轮胎对象 组合 而成,形成了对象的组合关系。

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




课后练习

去做练习

上一页 下一页