本期内容:
- 封装(定义类的过程)
案例(存放家具) - 继承
- 多态
- 封装的补充
私有和公有权限
属性的分类(实例属性,类属性)
方法的分类(实例方法,类方法,静态方法)
封装案例一:
# 定义一个名为 HouseItem 的类,用于表示家具
class HouseItem:
'''家具类'''
def __init__(self, name, area):
'''初始化家具对象的属性'''
self.name = name # 家具的名称
self.area = area # 家具的占地面积
def __str__(self):
# 返回家具的描述信息字符串
return f'家具名字{self.name},占地面积{self.area}平米'
# 定义一个名为 House 的类,用于表示房子
class House:
'''房子类'''
def __init__(self, name, area):
self.name = name # 户型名称
self.total_area = area # 房子的总面积
self.free_area = area # 房子的剩余面积
self.item_list = [] # 存储房子中家具名称的列表
def __str__(self):
# 返回房子的详细描述字符串
return f"户型: {self.name}, 总面积:{self.total_area}平米, 剩余面积: {self.free_area} 平米, " \
f"家具名称列表: {self.item_list}"
def add_item(self, item): # item 是传入的家具对象
# 判断房子剩余面积是否大于家具占地面积
if self.free_area > item.area:
# 如果是,将家具名称添加到家具列表
self.item_list.append(item.name)
# 更新房子的剩余面积
self.free_area -= item.area
print(f'{item.name}添加成功')
else:
print('剩余面积不足,换个大房子吧')
# 创建一个名为“席梦思”的床对象,占地面积 4 平米
bed = HouseItem('席梦思', 4)
# 创建一个名为“衣柜”的衣柜对象,占地面积 2 平米
chest = HouseItem('衣柜', 2)
# 创建一个名为“餐桌”的餐桌对象,占地面积 1.5 平米
table = HouseItem('餐桌', 1.5)
# 打印床的信息
print(bed) # 输出: 家具名字席梦思,占地面积4平米
# 打印衣柜的信息
print(chest) # 输出: 家具名字衣柜,占地面积2平米
# 打印餐桌的信息
print(table) # 输出: 家具名字餐桌,占地面积1.5平米
# 创建一个户型为“三室一厅”,总面积为 150 平米的房子对象
house = House('三室一厅', 150)
# 打印房子的初始信息
print(house) # 输出: 户型: 三室一厅, 总面积:150平米, 剩余面积: 150 平米, 家具名称列表: []
# 尝试向房子中添加床
house.add_item(bed)
# 再次打印房子的信息,查看添加床后的变化
print(house) # 输出: 床添加成功 户型: 三室一厅, 总面积:150平米, 剩余面积: 146 平米, 家具名称列表: ['席梦思']
封装案例二:
# 定义一个名为 LoginPage 的类,用于表示登录页面相关的操作
class LoginPage:
# 类的初始化方法,当创建 LoginPage 类的对象时会自动调用这个方法
# 它接受三个参数:username(用户名)、password(密码)、code(验证码)
def __init__(self, username, password, code):
self.username = username # 将传入的用户名参数赋值给对象的 username 属性
self.password = password # 将传入的密码参数赋值给对象的 password 属性
self.code = code # 将传入的验证码参数赋值给对象的 code 属性
self.btn = '登录' # 给对象的 btn 属性赋值为 '登录' 字符串,可能表示登录按钮的文本
# 定义一个名为 login 的方法,用于执行登录相关的操作
def login(self):
print(f'1.输入用户名{self.username}') # 打印输入用户名的操作提示,并显示实际的用户名
print(f'2.输入用户名{self.password}') # 这里应该是打印输入密码的操作提示,但错误地使用了用户名的格式化字符串,会显示密码内容
print(f'3.输入用户名{self.code}') # 这里应该是打印输入验证码的操作提示,但错误地使用了用户名的格式化字符串,会显示验证码内容
print(f'4.输入用户名{self.btn}') # 这里应该是打印输入其他相关内容(比如点击登录按钮)的操作提示,但错误地使用了用户名的格式化字符串,会显示登录按钮的文本
# 创建 LoginPage 类的一个对象,并传入具体的用户名、密码和验证码值
login = LoginPage('admin', '123456', '8888')
# 调用对象的 login 方法,执行登录相关操作
login.login()
'''运行结果
1.输入用户名admin
2.输入用户名123456
3.输入用户名8888
4.输入用户名登录
'''
私有和公有
- 在python中定义的方法和属性,可以添加访问控制权限(即在什么地方可以使用这个属性和方法)
- 访问控制权分为两种
– 公有权限
– 私有权限 - 公有权限
– 直接书写的方法和属性,都是公有的
– 公有的方法和属性,可以在任意地方访问和使用 - 私有权限
– 在类内部,属性名或者方法名,前面要加上两个下划线“__”,这个属性或者方法就变为私有的
– 私有的方法和属性,只能在当前类的内部使用 - 什么时候定义私有
– 1. 某个属性或者方法,不想在类外部被访问和使用,就将其定义为私有权限
– 2. 在软件测试中,一般不怎么使用,直接使用公有权限即可
– 3. 在开发中,或根据需求文档,确定什么作为私有 - 如果想要在类外部操作私有属性,方法是:在类内部定义公有的方法,我们通过这个公有方法去操作
补充:
对象.__dict__ :魔法属性,可以将对象具有的属性组成字典返回
案例:
定义一个 person 类,属性 name ,age (私有)
代码:
# 定义一个名为 Person 的类,用于表示人的相关信息
class Person:
# 类的初始化方法,当创建 Person 类的对象时会自动调用这个方法
# 它接受两个参数:name(姓名)、age(年龄)
def __init__(self, name, age):
self.name = name # 将传入的姓名参数赋值给对象的 name 属性
'''
1. 私有的本质是python解释器执行代码,发现属性名或者方法名前面有两个下划线,会将这个名字重命名。
这句话的意思是,当 Python 解释器在执行代码时,如果发现某个属性或方法的名字前面有两个下划线(例如 __age),
它会自动对这个名字进行重命名。这种机制的目的是为了防止子类意外覆盖父类的私有属性或方法。
2. 会在这个名字的前边加上 _类名 前缀,即self.__age ===> self._Person__age
这句话具体说明了名称改写的方式。当一个属性或方法的名字前面有两个下划线时,Python 解释器会在这个名字的前面加上 _类名 前缀。
例如:
如果类名是 Person,并且有一个私有属性 __age,那么这个属性在内部会被改写为 _Person__age。
这样一来,即使在子类中定义了一个同名的属性或方法(例如 __age),也不会覆盖父类的私有属性或方法,因为它们的名字已经被改写了。
'''
self.__age = age # 将传入的年龄参数赋值给对象的私有属性 __age
# 定义一个名为 __str__ 的方法,用于返回对象的字符串表示形式
def __str__(self):
# 返回包含姓名和年龄信息的字符串
return f',名字:{self.name},年龄:{self.__age}'
# 调用 Person 类来创建一个实例对象,并传入具体的姓名和年龄值
xm = Person('小明', 18)
# 打印对象 xm,会自动调用 __str__ 方法,输出对象的字符串表示形式
print(xm) # 输出:名字:小明,年龄:18
# 在类外部直接访问私有属性 __age 会报错,在类外部不能直接使用私有属性
# print(xm.__age) # AttributeError: 'Person' object has no attribute '__age'
# 直接修改 age 属性,这并不是修改私有属性,而是创建了一个新的公有属性 __age
xm.__age = 20
# 打印对象 xm,由于 __str__ 方法中使用的是私有属性 __age,所以这里输出的是原来的私有属性值
print(xm) # 输出:,名字:小明,年龄:18
# 通过名称改写的方式可以访问私有属性 __age
print(xm._Person__age) # 输出:18
# 通过名称改写的方式修改私有属性 __age 的值
xm._Person__age = 19
# 打印对象 xm,由于 __str__ 方法中使用的是私有属性 __age,所以这里输出的是修改后的私有属性值
print(xm) # 输出:,名字:小明,年龄:19
print(xm.__age) # 输出:20 调用的公有__age属性
继承
- 继承描述的是类与类之间的关系
- 继承的好处:
减少代码的冗余(相同的代码不需要多次重复书写),可以直接使用
语法
# 定义一个名为A的类,没有显式指定父类
# 在Python 3中,如果没有指定父类,默认继承自object类
# object 类是 Python 中最顶级(原始)的类
class A:
pass # pass语句表示什么都不做,用于占位,使代码结构完整
# 定义一个名为B的类,显式指定其父类为A
# 这意味着类B继承了类A的所有属性和方法
class B(A):
pass # 同样,pass语句用于占位,表示类B目前没有额外的定义
术语:
- 上述代码中的A类,称为 父类(基类)
- B类,称为 子类(派生类)
单继承:
一个类只继承一个父类,称为单继承
继承之后的特点:
子类(B)继承父类(A)之后,子类的对象可以直接使用父类中定义的公有属性和方法
案例:
- 定义一个动物类 吃
- 定义一个狗类,继承动物类中的吃,叫
- 定义一个哮天犬类,继承狗类
代码:
# 1. 定义一个名为 Animal 的类,表示一种动物,具有吃东西的行为
class Animal:
def eat(self):
print('吃') # 定义一个名为 eat 的方法,表示动物吃东西的行为
# 2. 定义一个名为 Dog 的类,继承自 Animal 类,表示一种具体的动物
class Dog(Animal):
def bark(self):
print('汪汪汪叫...') # 定义一个名为 bark 的方法,表示狗特有的叫声
# 3. 定义一个名为 XTQ 的类,继承自 Dog 类,表示一种更具体的动物
class XTQ(Dog):
pass # 目前没有定义任何新的方法或属性
# 创建 XTQ 类的对象 xtq
xtq = XTQ()
# 调用 xtq 对象的 eat 方法,由于 eat 方法在 Animal 类中定义,因此会调用 Animal 类中的 eat 方法
xtq.eat() # 输出: 吃
# 调用 xtq 对象的 bark 方法,由于 bark 方法在 Dog 类中定义,因此会调用 Dog 类中的 bark 方法
xtq.bark() # 输出: 汪汪汪叫...
结论
python 中对象.方法()调用方法
- 先在自己的类中去找有没有这个方法,如果有直接调用
- 如果自己的类中没有,就去父类中找,如果有直接调用
- 如果父类依然没有,去父类的父类中查找,如果有直接调用
- ….
- 如果object类中有,直接调用,如果没有代码报错
重写
重写:在子类中定义了和父类中名字相同的方法,就是重写
重写的原因:父类中的方法,不能满足子类对象的需求,所以重写
重写之后的特点:调用子类字节的方法,不再调用父类中的方法
class Parent:
def greet(self):
print("Hello from Parent!")
class Child(Parent):
def greet(self): # 重写了父类的 greet 方法
print("Hello from Child!")
# 创建父类对象
parent_obj = Parent()
parent_obj.greet() # 输出: Hello from Parent!
# 创建子类对象
child_obj = Child()
child_obj.greet() # 输出: Hello from Child!
重写的方式:
1.覆盖(父类中功能完全抛弃,不要,重写书写)
2.扩展(父类中功能还会调用,只是添加一些新的功能)
使用较多
覆盖
在Python中,覆盖 和 重写 通常可以互换使用,因为它们描述的是同一件事情。不过,在某些语言或上下文中,覆盖 可能更强调“替换”的行为,而 重写 更强调“重新定义”的行为。
- 直接在子类中 定义和父类中名字相同的方法
- 直接在方法中书写新的代码
class Dog:
def bark(self):
print('汪汪汪叫.....')
class XTQ(Dog):
# XTQ 类bark 方法不再是汪汪汪叫, 改为 嗷嗷嗷叫
def bark(self):
print('嗷嗷嗷叫...')
xtq = XTQ()
xtq.bark()
扩展父类中的功能
- 直接在子类中 定义和父类中名字相同的方法
- 在合适的地方调用 父类中方法,super().方法()
- 书写添加的新功能
案例
# 定义一个名为 Dog 的类
class Dog:
def bark(self):
print('汪汪叫...') # 定义一个名为 bark 的方法,表示狗叫
print('汪汪汪叫...') # 输出两行汪汪叫的声音
# 定义一个名为 XTQ 的类,继承自 Dog 类
class XTQ(Dog):
# 重写 Dog 类中的 bark 方法
def bark(self):
print('嗷嗷嗷叫...') # 输出一行嗷嗷叫的声音
# 调用父类 Dog 中的 bark 方法
super().bark() # 使用 super() 函数调用父类的方法
print('嗷嗷嗷叫...') # 输出一行嗷嗷叫的声音
# 创建 XTQ 类的对象 xtq
xtq = XTQ()
# 调用 xtq 对象的 bark 方法
xtq.bark()
'''
嗷嗷嗷叫...
汪汪叫...
汪汪汪叫...
嗷嗷嗷叫...
'''
多态【了解】
- 是一种写代码,调用的一种技巧
- 同一个方法,传入不同的对象,执行得到不同的结果,这种现象称为是多态
- 多态 可以 增加代码的灵活度
哪个对象调用方法,就去自己的类中去查找这个方法,找不到就去父类中找
属性和方法
python 中一切皆为对象
即 使用 class 定义的类,也是一个对象
对象的划分
1.实例对象
- 通过 类名() 创建的对象,我们称为实例对象,简称实例
- 创建对象的过程称为是实例化
- 我们平时所说的对象就是指 实例对象(实例)
- 每个实例对象,都有自己的内存空间,在自己的内存空间中保存自己的属性(也叫实例属性)
2.类对象
- 类对象 就是 类,或者可以认为是 类名
- 类对象是python 解释器在代码执行过程中创建的
- 类对象的作用:
1. 使用类对象创建实例 类名(),
2. 类对象 也有自己的空间,可以保存一些属性值信息(类属性) - 在一个代码中,一个类只有一份内存空间
属性的划分
1.实例属性
- 概念
是实例对象具有的属性 - 定义和使用
在init方法中,使用self.属性名 = 属性值 定义
在方法中 使用 self.属性名 来获取(调用)
- 内存
实例属性,在每个实例中都存在一份 - 使用时机
1. 基本上百分之九十九都是实例属性,即通过self 去定义的
2. 找多个对象,来判断这个值是不是都是一样的,如果都是一样的,同时变化,则一般定义为类属性(所有对象共享的属性),否则定义为实例属性(每个对象独有的属性)
2.类属性
- 概念:
类属性就是类的共享变量,它属于类本身,而不是某个具体的实例。所有实例都可以访问它,而且它们的值是共享的。 - 定义和使用
在类内部、方法外部,直接定义的变量–就是类属性
使用:类对象.属性名 = 属性值 or 类名.属性名 = 属性值
类对象.属性名 or 类名.属性名
class Dog:
species = "犬科动物" # 类属性
# 创建两个实例
dog1 = Dog()
dog2 = Dog()
# 访问类属性
print(dog1.species) # 输出: 犬科动物
print(dog2.species) # 输出: 犬科动物
# 修改类属性
Dog.species = "狼" # 通过类名修改类属性
# 再次访问类属性
print(dog1.species) # 输出: 狼
print(dog2.species) # 输出: 狼
- 内存
只有 类对象 中存在一份内存
方法的划分
方法:使用 def 关键字定义在类中的函数就是方法
1.实例方法【常用】
- 定义
在类中直接定义的方法,就是 实例方法
class Demo:
def func(self): # 参数一般写作 self,表示的是实例对象
pass - 定义时机
如果在方法中需要使用实例属性(即需要使用self),则这个方法必须定义为 实例方法 - 调用
对象.方法名() # 不需要给 self 传参
class MyClass:
class_attr = 10 # 类属性
@classmethod
def class_method(cls): # 类方法
return cls.class_attr
def instance_method(self): # 实例方法
return MyClass.class_attr
2.类方法(会用)
- 定义
在方法名字的上方书写 @classmethod 装饰器(使用@classmethod 装饰的方法)class Demo: @classmethod def func(cls): # 参数一般写作cls,表示的类对象(即类名)class pass
- 定义时机(什么时候用)
1. 如果你在方法中不需要使用实例属性(即self
),并且方法的行为与类的实例无关,那么类方法是更合适的选择。
2. 如果方法中只用到类属性(即类的共享变量,而不是实例的变量),那么你可以把这个方法定义为类方法(用@classmethod
),因为它不需要依赖实例。
3.但如果你愿意,也可以把它定义为实例方法(不用@classmethod
),因为即使通过实例调用,它也能访问类属性。 - 调用
1. 通过类对象调用
类名.方法名() # 也不需要个cls传参,python解释器自动传递
2. 通过实例对象调用
实例.方法名() # 也不需要给cls传参,python解释器自动传递
class MyClass:
@classmethod
def my_method(cls):
print(f"我是类方法,cls 是: {cls}")
# 通过类对象调用
MyClass.my_method()
# 打印结果: 我是类方法,cls 是: <class '__main__.MyClass'>
# 通过实例对象调用
obj = MyClass()
obj.my_method()
# 打印结果: 我是类方法,cls 是: <class '__main__.MyClass'>
在Python中,__main__
是一个特殊的字符串,表示当前模块是主程序(即直接运行的脚本)。它的作用是区分模块是被直接运行还是被导入。
- 简单来说:
1. 如果一个 Python 文件被直接运行,Python 会将该文件的__name__
属性设置为"__main__"
。
2. 如果一个 Python 文件被导入,Python 会将该文件的__name__
属性设置为模块的名字(即文件名,去掉.py
)。
3.静态方法(基本不用)
- 定义
在方法名字的上方书写 @staticmethod 装饰器(使用@staticmethod装饰的方法)
class Demo:
@staticmethod
def func(): # 一般没有参数
pass - 定义时机
1. 前提是方法中不需要使用实例属性(即self)
2. 方法也不使用类属性时,可以将这个放啊定义为静态方法
- 调用
1. 通过类对象调用
类名.方法名()
2. 通过实例对象调用
实例.方法名()
练习
1.练习1
定义一个 Dog 类,定义一个类属性 count,用来记录创建该实例对象的个数。
即每创建一个对象,count 的值就要加1.
实例属性 name
代码示例
# 定义一个名为 Dog 的类
class Dog:
count = 0 # 定义类属性,用于记录创建的 Dog 实例的数量
def __init__(self, name): # 定义示例属性,在__init__构造函数,用于初始化 Dog 实例
self.name = name # 实例属性,存储 Dog 的名字
# 因为每创建一个对象,就会调用 init 方法, 就将个数加 1 的操作,写在 init 方法中
Dog.count += 1 # 每创建一个实例,类变量 count 增加 1
# 在类外部打印输出目前创建的几个对象
print(Dog.count) # 输出当前创建的 Dog 实例的数量
# 创建一个对象
dog1 = Dog('小花') # 创建一个名为小花的 Dog 实例
# 打印输出目前创建的几个对象
print(Dog.count) # 输出当前创建的 Dog 实例的数量
dog2 = Dog # 创建一个 Dog 类型的变量 dog2,但没有初始化名字,不是创建对象, 个数不变的
dog3 = dog1 # 将 dog1 的值赋给 dog3,因此 dog3 和 dog1 指向同一个实例,不是创建对象, 个数不变的
print(Dog.count) # 输出当前创建的 Dog 实例的数量
dog4 = Dog('大黄') # 创建一个名为大黄的 Dog 实例对象,个数加1
print(Dog.count) # 输出当前创建的 Dog 实例的数量 2
dog5 = Dog('小白') # 创建一个名为小白的 Dog 实例
print(Dog.count) # 输出当前创建的 Dog 实例的数量 3
# 补充, 可以使用 实例对象.类属性名 来获取类属性的值 (原因, 实例对象属性的查找顺序, 先在实例属性中找,找到直接使用
# 就是先找实例的属性,如果没有找到,再通过类名找类属性,如果还没有找到,就会报错。
# 没有找到会去类属性中 找, 找到了可以使用, 没有找到 报错)
print(dog1.count) # 输出 dog1 实例的 count 属性值,应为 1
print(dog4.count) # 输出 dog4 实例的 count 属性值,应为 2
print(dog5.count) # 输出 dog5 实例的 count 属性值,应为 3
2. 练习2
定义一个游戏类Game,包含实例属性 玩家名字(name)
- 要求记录游戏的最高分(top_score 类属性)
- 定义方法:show_help 显示游戏的帮助信息,输出这是 `这是游戏的帮助信息`
- 定义方法:show_top_score,打印输出游戏的最高分
- 定义方法:start_game,开始游戏,规则如下
1. 使用随机数获取本次游戏的得分,范围(10-100)之间
2. 判断本次得分和最高分之间的关系
– 如果本次得分比最高分高,则修改最高分
– 如果本次得分小于或者等于最高分,则不进行任何操作
3. 然后输出本次游戏的得分 - 主程序实现步骤
1. 创建一个 Game 对象 `小王`
2. 小王玩一次游戏
3. 查看历史最高分
4. 小王再玩一次游戏
5. 再次查看游戏最高分
6. 查看游戏的帮助信息
代码实现
import random # 导入random模块,用于生成随机数
class Game:
# 定义类属性top_score,用于记录游戏的最高分
top_score = 0
def __init__(self, name):
# 定义实例属性name,用于记录玩家的名字
self.name = name
def show_help(self):
# 定义方法show_help,用于打印游戏的帮助信息
print('这是游戏的帮助信息')
def show_top_score(self):
# 定义方法show_top_score,用于打印游戏的最高分
print(f'游戏的最高分为{Game.top_score}')
def start_game(self):
# 定义方法start_game,用于模拟游戏开始,并生成一个随机得分
print(f'{self.name}开始一局游戏,游戏中...', end='')
score = random.randint(10, 100) # 生成一个10到100之间的随机整数作为本次游戏的得分
print(f'本次游戏的得分为{score}')
if score > Game.top_score:
# 如果本次得分高于最高分,则更新最高分
Game.top_score = score
# 创建Game类的实例xw,名为小王
xw = Game('小王')
xw.start_game() # 调用start_game方法,开始游戏,输出随机得分
xw.show_help() # 调用show_help方法,显示帮助信息
xw.start_game() # 再次调用start_game方法,开始游戏,输出随机得分
xw.show_top_score() # 调用show_top_score方法,显示最高分
xw.show_help() # 调用show_help方法,显示帮助信息
使用类方法和静态方法
import random # 导入random模块,用于生成随机数
class Game:
# 定义类属性top_score,用于记录游戏的最高分
top_score = 0
def __init__(self, name):
# 定义实例属性name,用于记录玩家的名字
self.name = name
@staticmethod
def show_help():
# 定义静态方法show_help,用于打印游戏的帮助信息
# 静态方法不需要访问实例属性和类属性,可以直接通过类名调用
print('这是游戏的帮助信息')
@classmethod
def show_top_score(cls):
# 定义类方法show_top_score,用于打印游戏的最高分
# 类方法可以通过类名直接调用,并且可以访问类属性top_score
print(f'游戏的最高分为{Game.top_score}')
def start_game(self):
# 定义方法start_game,用于模拟游戏开始,并生成一个随机得分
print(f'{self.name}开始一局游戏,游戏中...', end='')
score = random.randint(10, 100) # 生成一个10到100之间的随机整数作为本次游戏的得分
print(f'本次游戏的得分为{score}')
if score > Game.top_score:
# 如果本次得分高于最高分,则更新最高分
Game.top_score = score
# 创建Game类的实例xw,名为小王
xw = Game('小王')
xw.start_game() # 调用start_game方法,开始游戏,输出随机得分
xw.show_help() # 调用show_help方法,显示帮助信息
xw.start_game() # 再次调用start_game方法,开始游戏,输出随机得分
xw.show_top_score() # 调用show_top_score方法,显示最高分
xw.show_help() # 调用show_help方法,显示帮助信息
THE END