转自:http://www.byhy.net/tut/py/basic/18/
什么是类
Python 中的一切对象都有各自的类型,比如
整数对象 的类型是 int
字符串对象 的类型是 str
列表对象 的类型是 list
元组对象 的类型是 tuple
字典对象 的类型是 dict
Python 的内置函数type可以查看对象的类型
>>> type(12)
# 整数类型
>>> type('12')
# 字符类型
>>> type([1,2])
# 列表类型
>>> type((1,2))
# 元组类型
>>> type({1:2})
# 字典类型
我们掌握了这些内置的数据类型,通常就可以开发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)
说明 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属性
十万分感谢白月黑羽平台