类和对象
- 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
- 特征被称为属性,行为被称为方法
- 对象是由类创建出来的一个具体实体,可以直接使用
- 由哪一个类创建出来的对象,就拥有在哪一个类中定义的属性和方法
- 类和对象的关系
- 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象
- 类只有一个,而对象可以有很多个
- 不同对象之间的属性可能会各不相同
- 类中定义了什么属性和方法,对象中就有什么属性和方法,不能多,也不能少
类的设计:通常需要满足一下三个要素
定义一个只包含方法的类,语法格式为
class 类名:
def 方法名(self, 参数列表)
方法体
方法的定义格式和之前学习过的函数几乎一样,区别在于第一个参数必须是self
在Python中,可以在类的外部直接设置一个属性,例如
对象名.name = 'Tom'
。但是不推荐使用,因为属性应该封装在类的内部,通常在初始化方法内部定义属性- 初始化方法
- 当使用
类名()
创建对象时,会自动执行以下操作- 创建对象:为对象在内存中分配空间
- 调用初始化方法:为对象的属性设置初始值
- 这个初始化方法就是init方法,是对象的内置方法,专门用来定义一个类具有哪些属性
- 当使用
- 在初始化方法内部定义属性
- 语法格式:self.属性名 = 属性的初始值
- 创建该类的对象时,对象中就会拥有该属性
创建对象的同时指定初始值
语法格式:对象变量 = 类名(参数列表)
- 使用类创建对象之后,对象变量保存的是对象在内存中的存放地址
- 因此使用print输出对象变量,默认是输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的十六进制地址,形如<__main__.A object at 0x0000020E675745B0>
访问对象和方法的语法格式
new:创建对象时,会被自动调用
- init:对象被初始化时,会被自动调用
- del:对象被从内存中销毁前,会被自动调用
- 如果希望在从内存销毁对象之前,再做一些事情,可以使用del方法
str:返回对象的描述信息,print 函数输出使用
- 如果希望使用print输出对象变量时,能够打印自定义的内容,可以使用str方法
注意:str方法必须返回一个字符串 ```python class Person: def init(self, name, age, height): self.name = name self.age = age self.height = height print(‘init‘)
def run(self): print(self.name + ‘ is running’)
def str(self): return self.name
def del(self): print(‘del‘)
xiaoming = Person(‘小明’, 18, 175) xiaoming.run() print(xiaoming) del xiaoming
“”” 输出: init 小明 is running 小明 del “””
<a name="quXe6"></a>
## 私有属性和私有方法
- 应用场景
- 对象的某些属性或方法只希望在对象的内部被访问,而不希望在外部被访问
- 私有属性就是对象不希望公开的属性,私有方法就是对象不希望公开的方法
- 定义方式:在定义属性或方法时,在属性名或者方法名前增加两个下划线即可
<a name="Qg9mT"></a>
## 封装性
- 特点:将属性和方法封装到一个抽象的类的内部
- 使用类创建对象,然后通过对象调用属性和方法
```python
class HouseItem:
def __init__(self, name, area):
self.name = name
self.area = area
def __str__(self):
return "%s,占地%.2f" % (self.name, self.area)
class House:
def __init__(self, house_type, area):
self.house_type = house_type
self.area = area
self.free_area = area # 剩余面积
self.item_list = [] # 家具列表
def __str__(self):
return ("户型:%s\n总面积:%.2f\n剩余面积:%.2f\n家具:%s"
% (self.house_type, self.area,
self.free_area, self.item_list))
# 在一个形参后通过冒号加注解,能够联想,提高编程效率
def add_item(self, item: HouseItem):
print("要添加%s" % item) # 很精妙,会调用HouseItem类的__str__方法
if self.free_area < item.area:
print('房间面积不够')
return
self.item_list.append(item.name)
self.free_area -= item.area
# 创建家具对象
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
# 创建房子对象
my_home = House("两室一厅", 60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
"""
输出:
要添加席梦思,占地4.00
要添加衣柜,占地2.00
要添加餐桌,占地1.50
户型:两室一厅
总面积:60.00
剩余面积:52.50
家具:['席梦思', '衣柜', '餐桌']
"""
继承性
- 概念:子类继承父类,从而拥有父类的所有属性和方法
-
单继承
语法格式:class 子类名(父类名):
- 子类继承父类,可以直接享受父类中已经封装好的方法,不需要再次开发
- 继承的传递性:子类拥有父类以及父类的父类中封装的所有属性和方法
- 说明:在Python3中定义类时,如果没有指定父类,会默认使用object类作为该类的基类
- 一些术语
- 父类 / 基类
- 子类 / 派生类
- 继承 / 派生
- 方法的重写:当父类的方法实现不能满足子类的需求时,可以对所继承的方法进行重写(override),重写的两种情况:
- 覆盖父类的方法:在子类中重新编写父类的方法实现,适用于子类的方法实现和父类的方法实现完全不同的情况
- 重写之后,在运行时只会调用子类中重写的方法,而不会调用父类中封装的方法
- 对父类方法进行扩展:父类原本封装的方法实现是子类方法实现的一部分,适用于子类的方法实现中包含父类的方法实现
- 在需要的位置使用
super().父类方法
来调用父类方法的执行 - 而在代码的其他位置,针对子类的需求,编写子类特有的实现代码
- 在需要的位置使用
- 覆盖父类的方法:在子类中重新编写父类的方法实现,适用于子类的方法实现和父类的方法实现完全不同的情况
- 关于super
- 在Python中super是一个特殊的类,super()就是使用super类创建出来的对象,即父类的匿名对象
- 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
关于父类的私有属性和私有方法
子类可以继承多个父类,并且具有所有父类的属性和方法
- 语法格式:class 子类名(父类名1, 父类名2, …):
- 问题:如果不同的父类中存在同名的方法或属性,子类对象在调用时,调用的是哪一个
- Python中针对类提供了一个内置属性mro可以查看方法和属性的调用顺序
- MRO是method resolution order,用于多继承时判断方法或属性的调用顺序
- 使用方式:类名.mro
- 遵循的准则:子类会先于父类被检查,多个父类会根据它们在子类继承列表中的顺序被检查
- 举例
- Python中针对类提供了一个内置属性mro可以查看方法和属性的调用顺序
- 多态:不同的子类对象调用相同的父类方法,产生不同的执行结果
- 多态以继承和重写父类方法为前提,可以增加代码的灵活度,且不会影响到类的内部设计
```python
class Dog:
def init(self, name):
def game(self):self.name = name
print("%s蹦蹦跳跳的玩耍" % self.name)
class XiaoTianDog(Dog): def game(self): print(“%s飞到天上玩耍” % self.name)
class Person: def init(self, name): self.name = name def game_with_dog(self, dog): print(“%s和%s快乐的玩耍” % (self.name, dog.name)) dog.game()
wangcai = XiaoTianDog(“飞天旺财”) xiaoming = Person(“小明”) xiaoming.game_with_dog(wangcai)
“”” 输出: 小明和飞天旺财快乐的玩耍 飞天旺财飞到天上去玩耍 “””
<a name="BN4jV"></a>
## 类属性和类方法
- 一些术语
- 类的实例:创建出来的对象
- 实例化:创建对象的动作
- 实例属性:对象的属性
- 实例方法:对象的方法
- 每一个对象都有自己独立的内存空间,保存各自不同的属性
- 而多个对象的方法,在内存中只有一份。在调用方法时,需要把对象的引用传递到方法内部,因此定义方法时有self参数
- 在Python中,类是一个特殊的对象 ----> 类对象。在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个实例对象
- 类对象拥有自己的属性和方法,即类属性和类方法,可以通过`类名.`的方式访问类的属性或者调用类的方法
![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654620136817-843afabe-eda0-4a0e-bba6-87982b5d5a59.png#clientId=u82e40517-ede0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=188&id=uc08eb5d1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=282&originWidth=811&originalType=binary&ratio=1&rotation=0&showTitle=false&size=133178&status=done&style=none&taskId=u7fe09ba4-a67f-4ca2-ad9d-d3cf218f67f&title=&width=540.6666666666666)
<a name="Ai2xu"></a>
### 类属性
- 类属性就是给类对象定义的属性,通常用来记录与这个类相关的特征,类属性不会用于记录具体对象的特征
- 定义类属性的语法格式:使用赋值语句在class关键字下方可以定义类属性
- 属性的获取机制:通过`对象名.属性名`首先是在对象内部查找对象属性,如果没有找到再向上查找类属性,因此访问类属性有两种方式
- 类名.类属性
- 对象名.类属性(不推荐)
- 注意:如果使用`对象名.类属性 = 值`赋值语句,只会给对象添加一个与类属性同名的对象属性,而不会影响到类属性的值
```python
class Tool:
# 类属性
count = 0
def __init__(self, name):
self.name = name
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")
print(Tool.count) # 3
tool1.count = 99 # 给对象tool1新增了一个count的对象属性
print(tool3.count) # 3
print(Tool.count) # 3
类方法
- 类方法就是针对类对象定义的方法,在类方法内部可以直接访问类属性或者调用其他的类方法
- 定义类方法的语法格式
- 类方法需要使用修饰器 @classmethod 来标识,告诉解释器这是一个类方法
- 类方法的第一个参数应该是cls
- 由哪一个类调用的方法,方法内的cls就是哪一个类的引用
- 与实例方法的第一个参数是self的意义类似
- 也可以使用其他名称,不过习惯使用cls
- 通过
类名.类方法名
调用类方法时,不需要传递cls参数 在类方法内部,可以使用
cls.
访问类的属性和调用其他的类方法 ```python class Tool:类属性
count = 0
def init(self, name): self.name = name Tool.count += 1
@classmethod def show_tool_count(cls): print(cls.count)
tool1 = Tool(“斧头”) tool2 = Tool(“榔头”) tool3 = Tool(“铁锹”)
Tool.show_tool_count() # 输出3
<a name="zVNCR"></a>
### 静态方法
- 如果需要在类中封装一个方法,这个方法既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法,那么可以将这个方法封装成一个静态方法,例如打印一些提示信息
- 定义静态方法的语法格式
![image.png](https://cdn.nlark.com/yuque/0/2022/png/12692524/1654652542595-05c819a7-da45-4d68-98d5-816fbba04cf4.png#clientId=ub089f1ef-e9ea-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=60&id=u0bfb415d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=90&originWidth=190&originalType=binary&ratio=1&rotation=0&showTitle=false&size=4678&status=done&style=none&taskId=ua6d32ea3-171f-4d01-8f50-7d8bde5b3cd&title=&width=126.66666666666667)
- 通过`类名.静态方法名`调用静态方法
- 比较实例方法、类方法、静态方法
- 实例方法:方法内部需要访问实例属性
- 实例方法内部可以使用`类名.类属性`访问类属性
- 如果方法内部既需要访问实例属性,又需要访问类属性,应该定义成实例方法
- 类方法:方法内部只需要访问类属性
- 静态方法:方法内部不需要访问实例属性和类属性
- 注意:静态方法内部可以访问类属性,但是由于参数列表为空,所以不能访问实例属性
- 静态方法也可以调用类方法,但是在Pycharm中可能会有点小bug
```python
class Game(object):
top_score = 0
def __init__(self, player_name):
self.player_name = player_name
@staticmethod
def show_help():
print("帮助信息:让僵尸进入大门")
@classmethod
def show_top_score(cls):
print("历史记录:%d" % cls.top_score)
def start_game(self):
print("%s开始游戏啦" % self.player_name)
Game.show_help()
Game.show_top_score()
game = Game("小明")
game.start_game()