一、类与对象
面向过程和面向对象都是一种解决问题的编程思想,前者功能的实现主要依赖于“函数”,而后者的落地需要借助“类”和“对象”来实现。因此,面向对象编程(object-oriented programming, OOP)就是对“类”和“对象”的合理使用,并且它具有封装、继承和多态三大特性。面向对象就是将编程当作一个事物,对外界来说,事物是可以直接使用的,不需要去关心它的内部情况。
1.1 面向对象与面向过程
假设我们现在需要在一块空地上盖一栋学生宿舍,面向对象和面向过程实现该需求的方式有何不同?
![学生宿舍立方图](https://img-blog.csdnimg.cn/381717865795443fadcb5960ffaacb97.jpg)
①面向过程: 角色上更像是一名执行者,按照建筑施工流程和建筑施工图依次开展工作,如申请用地、土方开挖、框架搭建等;
![面向过程编程](https://img-blog.csdnimg.cn/38f39a2b75b2405ab077907d68984fd0.jpg)
- 实现需求时,喜欢倾向于过程与步骤,不注重职责分工;
- 根据需求,将某些功能独立的代码封装成一个又一个的函数;
- 将数据与函数按照执行逻辑顺序组织在一起,数据与函数分开对待;
②面向对象: 角色上更像是一名指挥者,找一个可以胜任的建筑施工项目经理,详细的施工步骤交给他去处理;
![面向对象编程](https://img-blog.csdnimg.cn/ed87320c0bae4d1dbbb5bfa184f3f37a.jpg)
- 注重对象和职责,不同的对象承担不同的职责;
- 根据需求,首先确定职责,然后根据职责创建不同的对象;
- 对象内部封装不同的属性和方法,顺序调用不同对象的不同方法;
1.2 类与对象的关系
1.2.1 类的概念
类就是一系列具有相同特征和行为事物的统称,是抽象的,并非真实存在的。例如,造飞机的图纸(下图)就相当于一个类,它规定了未来生产出来的飞机应该具有哪些特征(型号、大小等)和行为(起飞、飞行等),但它并不是一个具体的飞机,而是飞机的一个抽象概念,即模板。
![飞机模型(类)](https://img-blog.csdnimg.cn/539a4cde4f6f4c7cb3592d9e85447c9b.png)
- 特征就是一个变量,在类中我们称之为属性;
- 行为其实就是一个函数,在类中我们称之为方法;
- 类,本质上就是由属性和方法组成的抽象概念,不能直接使用;
1.2.2 对象的概念
对象,是由类创建出的真实存在的事物,可以直接使用类中定义的公共属性和方法,如F22(猛禽)、F117(夜莺)等,这些不同的飞机就是通过类(图纸)创建出来的对象。注意:实际开发过程中,先存在类,然后才有对象,并且同一个类可以实例化出多个不同的对象。
![实际战斗机](https://img-blog.csdnimg.cn/084a5ce874f04991a00996f433b1d80e.png)
1.3 面向对象简单实现
1.3.1 新式类与经典类
在Python中,object
是所有类的父类,它提供了一些内置的属性和方法,可使用Python内置函数dir()
进行查看,不过它在Python 2.x
和Python 3.x
中的定义和使用有所区别:
- 新式类: 以
object
为基类的类,Python 3.x
后定义类时,如果没有显式指定父类,则默认继承自object
,它们也叫新式类; - 经典类: 不以
object
为基类的类,Python 2.x
中定义类时,如果没有显式指定继承的类,则不会继承object
,所以它们也称为旧式类;
Python 3.x 中创建新式类:
class Person(object): # 显式指定继承自object;
"""
Do something!
"""
pass
class Person: # 没有显式指定,但仍默认继承自object;
"""
Do something!
"""
pass
Python 2.x 中创建经典类:
class Person(object): # 显式指定父类为object;
"""
Do something!
"""
pass
class Person: # 没有显式指定,默认不继承自object;
"""
Do something!
"""
pass
1.3.2 类的定义与对象的创建
①类的定义: 谨记一定是先有类后有对象,并且后期除非明确声明当前定义的类继承自某个类,否则一律使用新式类;
# 新式类定义格式;
class 类名(object): # 类名的命名需要做到见名知义并且遵循大驼峰原则(每个单词首字母大写,其它字母小写);
# 属性或特征;
pass
# 方法或行为;
pass
# 定义一个简单的飞机模型类;
class ModelPlane(object):
# 飞机具有颜色color和尺寸size属性,这里的属性是类属性(后面会讲),即所有基于该类创建出的对象都默认拥有这些属性;
color = "red"
size = (100, 20)
# 飞机具有飞行行为;
def fly(self):
print("我正在飞翔!")
# 飞机具有射击敌人的行为;
def shot(self):
print("我正在射击敌人!")
②创建对象: 在Python中,创建对象的过程也可以称为实例化对象的过程,也就是将抽象的类转为实际存在的事物的过程;
# 实例化对象格式;
对象名 = 类名()
# 创建对象;
F117 = ModelPlane()
# 直接打印该对象会调用它所属类的 __str__()方法,该方法默认输出对象的内存地址信息,它以0x开头,表示十六进制;
print(F117) # <__main__.ModelPlane object at 0x000001F25C6FA400>
③self参数剖析: 飞机模型类中的方法fly()
和shot()
中都有一个形参self
,Python解释器会自动将其指向调用该函数的对象;
class ModelPlane(object):
# 飞机具有飞行行为;
def fly(self):
print("我正在飞翔!")
print("self指向:", id(self)) # <-- 使用python内置函数id()可以直接查看一个对象的内存地址;
# 飞机具有射击敌人的行为;
def shot(self):
print("我正在射击敌人!")
print("self指向:", id(self)) # <-- 使用python内置函数id()可以直接查看一个对象的内存地址;
# 创建夜莺对象F117
F117 = ModelPlane()
print("夜莺F117的内存地址:", id(F117)) # 夜莺F117的内存地址: 1230523307008
F117.fly() # Python解释器会自动将对象F117传给fly()中的self;
"""
我正在飞翔!
self指向: 1230523307008
"""
# 创建猛禽对象F22; --> id(F22)和类中方法中的id(self)相等;
F22 = ModelPlane()
print("猛禽F22的内存地址为:", id(F22)) # 猛禽F22的内存地址为: 1230523558256
ModelPlane.shot(F22) # 我们也可以手动通过【类名.方法名(对象名)】这种方式,将对象F22传给shot()中的形参self;
"""
我正在射击敌人!
self指向: 1230523558256
"""
1.3.3 添加和访问对象属性
①类外面添加和访问对象属性: 对象创建成功后,通过对象名.属性名=属性值
即可为对象添加一个新属性或对同名属性值进行覆盖;
class ModelPlane(object):
# 飞机具有飞行的行为;
def fly(self):
print("我正在飞翔!")
# 飞机具有射击敌人的行为;
def shot(self):
print("我正在射击敌人!")
# 实例化对象;
F35 = ModelPlane()
# 通过【对象名.属性名=属性值】的方式即可为一个对象添加新属性;
F35.color = "黑色"
F35.size = (100, 20)
# 通过【对象名.属性名】的方式即可访问或获取一个对象的属性;
print(f"类外面为对象F35添加属性: color={F35.color}, size={F35.size}!") # 类外面为对象F35添加属性: color=黑色, size=(100, 20)!
# 如果一个对象的属性已经存在,此时【对象名.属性名=属性值】的操作是对该属性的值进行修改;
F35.color = "红色"
print(f"对象F35现在的颜色color为:", F35.color) # 对象F35现在的颜色color为: 红色
②类里面访问对象属性: 通过self.属性名
即可在类内部调用对象所有公开的属性;
class ModelPlane(object):
# 飞机具有飞行行为;
def fly(self):
print("我正在飞翔!")
# 飞机具有射击敌人的行为;
def shot(self):
print("颜色为%s的%s正在射击敌人!" % (self.color, self.name)) # <-- self.属性名即可使用类中属性;
# 实例化对象F35;
F35 = ModelPlane()
F35.name = "F35"
F35.color = "黑色"
F35.shot() # 颜色为red的F35正在射击敌人!
1.4 面向对象常用功能
纵观大千世界,任何一个事物从破土而出、蓬勃发展到最后的日落西山都需要遵循大自然客观发展规律。在面向对象编程的过程中,每一个对象似乎也面临这样的命运:创建、初始化、被使用、垃圾回收。像其它面向对象编程语言一样,Python中的类也支持各种各样的属性和方法,如对象属性和对象方法、类属性和类方法、私有属性和私有方法、静态方法以及魔法方法等,这些不同属性和方法的有机结合保证了对象各阶段的正常运行。魔法方法,以两个下划线开始并且以两个下划线结束的方法(__xxx__()
),它们具有特殊的功能,会在适当的时刻被激活,准确高效地使用它们,可以大大提高我们的编程效率。
1.4.1 实例属性与实例方法
可以想象,如果我们在定义类时设置了固定的属性值,如飞机类中的color和size,则利用该类实例化出的每个对象属性值都是相同的,即它们的color都为red,size都为(100, 20)。此外,实际生活中,不同对象的属性值是不同的,比如每个人都会有属于自己的名字和年龄。那么,除了在创建出对象后通过外部添加和修改属性外,我们是否可以在创建一个对象时就直接为其传入属性值?接下来,让我们先来学习第一个魔法方法:__init__(self)
,它的作用是对不同对象的属性进行初始化设置。
①无参数的__init__(self): 构造函数,创建对象时默认被调用,不需要手动调用,其中self
参数指向当前调用对象;
class Demo(object):
def __init__(self):
print("我是构造函数,用于初始化对象!")
# 创建对象时Python解释器主动调用__init__()方法,并将当前对象(demo)传给函数形参(self);
demo = Demo() # 我是构造函数,用于初始化对象!
②有参数的__init__(self): 一个类可以创建出多个对象,不同对象的属性各自保存,实例方法是所有对象共享的,只占用一份内存空间;
class Student(object):
# 构造函数__init__()内的属性称为对象属性或实例属性,不同对象保存各自的属性及属性值;
def __init__(self, name, age):
self.name = name
self.age = age
def study(self): # 对象方法为所有对象共有,Python解释器会将self指向当前函数调用对象;
print("我是%s,今年%d岁,我正在吃海底捞!" % (self.name, self.age)) # 类内部通过self调用属性值;
# 实例化对象stu1;
stu1 = Student("海波东", 30) # 创建对象的时候需要为属性name和age传入值;
print("对象stu1的属性值为:", stu1.name, ",", stu1.age) # 对象stu1的属性值为: 海波东, 30
stu1.study() # 我是海波东,今年30岁,我正在吃海底捞!
# 实例化对象stu2;
stu2 = Student("美杜莎", 25)
print("对象stu2的属性值为:", stu2.name, ",", stu2.age) # 对象stu2的属性值为: 美杜莎, 25
stu2.study() # 我是美杜莎,今年25岁,我正在吃海底捞!
③内置属性__slots__: 用来规定对象可以存在的属性,即实例化后的对象不能再在外部通过对象名.属性=属性值
为其添加新的对象属性;
class Student(object):
__slots__ = ("name", "age") # __slots__是一个元组,它可以传入需要限制的属性,每个属性以字符串表示,多个属性之间以逗号间隔;
def __init__(self, name, age):
self.name = name
self.age = age
def study(self):
print("我是%s,今年%d岁,我正在吃海底捞!" % (self.name, self.age))
# 实例化对象stu3;
stu3 = Student("海波东", 30)
print("对象stu3原始属性:", stu3.name, ",", stu3.age) # 对象stu3原始属性: 海波东 , 30
# 对象stu3外部修改已有属性值;
stu3.name = "萧炎"
stu3.age = 20
print("对象stu3外部修改后的属性:", stu3.name, ",", stu3.age) # 对象stu3外部修改后的属性: 萧炎 , 20
# 对象stu3增加不存在属性 --> AttributeError(属性错误)
stu3.spouse = "云韵"
![__slots__属性限制](https://img-blog.csdnimg.cn/532b66679685449eaac1b6a959fb5baf.png)
1.4.2 打印对象信息
通过前面的示例可以看到,直接使用print()
函数打印一个类的实例对象会在终端返回它的所属类名和内存地址信息,其实这是调用该对象的__str__()
魔法方法的结果。当然,我们也可以重写该方法以返回与该对象有关的描述信息,比如它的属性等。
①__str__(self): 该方法的第一个形参self
代表当前调用对象,不需要传递其它参数,并且它必须返回(return
)一个字符串格式的内容;
class Figure(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 重写__str__()方法;
def __str__(self):
return "我是{}, 今年{}岁了!".format(self.name, self.age)
# 主程序入口;
if __name__ == '__main__':
# 实例化对象;
girl = Figure("云韵", 25)
print(girl) # 我是云韵, 今年25岁了!
# 实例化对象;
boy = Figure("萧炎", 20)
# 调用内置函数str()会触发该对象的__str__()方法;
print(str(boy)) # 我是萧炎, 今年20岁了!
②__repr__(): 该方法与__str__()
都可以输出对象的描述信息,不同的是,在交互式环境中直接输入对象即可调用该方法,无需print()
;
class Figure(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 重写__repr__()方法;
def __repr__(self):
return "我是斗破苍穹中的{}, 今年{}岁!".format(self.name, self.age) # 返回值一定是字符串格式;
# 主程序入口;
if __name__ == '__main__':
# 实例化对象;
girl = Figure("云韵", 25)
print(girl)
# 实例化对象;
boy = Figure("萧炎", 20)
print(repr(boy)) # 调用内置函数repr()会触发该对象的__repr__(self)方法;
- 注意: 如果一个对象既重写了
__str__()
方法,又重写了__repr__()
方法,Python解释器会先执行__str__()
方法,并且会在终端打印;
class Figure(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 重写__str__()方法;
def __str__(self):
return "我是__str__()方法,我被调用了!"
# 重写__repr__()方法;
def __repr__(self):
return "我是__repr__()方法,我被调用了!"
# 主程序入口;
if __name__ == '__main__':
# 实例化对象;
girl = Figure("云韵", 25)
print(girl) # 我是__str__()方法,我被调用了!
# 实例化对象;
boy = Figure("萧炎", 20)
# 调用内置函数repr()会触发该对象的__repr__(self)方法;
print(repr(boy)) # 我是__repr__()方法,我被调用了!
- 注意: 交互式环境下,如果一个类同时定义了
__str__()
方法和__repr__()
方法,调用对象执行后者,打印对象执行前者,如下所示。
![__repr__()在交互式环境中的使用](https://img-blog.csdnimg.cn/214f9ffce24d4ffe8e42cdf06e837069.png)
1.4.3 判断两个对象是否相等
在Python中,我们可以通过身份运算符is
和比较运算符==
来比较两个对象是否属于同一个对象,默认情况下它们都是比较两个对象的内存地址。不同的是,比较运算符==
可以通过重写对象所属类的__eq__()
方法来自定义比较规则,这是我们要学习的第二个魔法方法。
①比较对象的内存地址:
# 先定义一个比较两个对象的函数,里面可以打印它们的比较结果和内存地址;
def compare(ele1, ele2):
print(f"对象{ele1}和{ele2}的is比较结果为:", ele1 is ele2)
print(f"对象{ele1}和{ele2}的==比较结果为:", ele1 is ele2)
print("{}和{}的id分别为:".format(ele1, ele2), id(ele1), id(ele2))
- 常见Python对象的比较: 整形(int)、字符串(str)、列表(list)和字典(dict)等;
# 调用函数,传入对象(这里只以字符串为例,其它对象同理)
compare("Hello", "hello") # 两个不同对象;
"""
对象Hello和hello的is比较结果为: False
对象Hello和hello的==比较结果为: False
Hello和hello的id分别为: 2076256308336 2076256307952
"""
compare("Hello", "Hello") # 两个相同对象;
"""
对象Hello和Hello的is比较结果为: True
对象Hello和Hello的==比较结果为: True
Hello和Hello的id分别为: 2076256308336 2076256308336
"""
- 自定义对象比较: 这里我们规定,只要对象属性中的name和age相等,我们就认为它们是同一个对象;
# 定义一个Person类,它具有name和age属性;
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 实例化两个对象(属性不同)
p1 = Person("小明", 25)
p2 = Person("小红", 26)
print(p1 is p2) # False
print(p1 == p2) # False
print("p1和p2的内存地址为:", id(p1), id(p2)) # p1和p2的内存地址为: 2303743198208, 2303747728720
# 实例化两个对象(属性相同)
p3 = Person("Jack", 20)
p4 = Person("Jack", 20)
print(p3 is p4) # False
print(p3 == p4) # False
print("p3和p4的内存地址为:", id(p3), id(p4)) # p3和p4的内存地址为: 1465532253184,1465532589392
②自定义比较规则: 显然默认比较规则无法比较对象的属性,需要重写对象所属类中的__eq__()
方法,它属于比较运算符魔法方法;
# 重写Person类中的__eq__()方法;
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 只要两个对象的内容(属性)相同就认为它们相等;
def __eq__(self, other):
return (self.name == other.name) and (self.age == other.age)
# 重新拿来上面的的p3和p4对象;
p3 = Person("Jack", 20)
p4 = Person("Jack", 20)
print(p3 is p4) # False
print(p3 == p4) # True
print("p3和p4的内存地址为:", id(p3), id(p4)) # p3和p4的内存地址为: 2688001868800 , 2688002205008
1.4.4 类属性与类方法
①类属性: 在Python中,类可以实例化一个对象,而其本身被称为类对象,类属性就是类对象拥有的属性,它被该类的所有实例对象所共有;
class Person(object):
"""
当记录的某项数据始终保持一致时,可以定义类属性,因为它仅占用一份内存,更加节省内存空间;
"""
type_ = "Human" # 类属性;
def __init__(self, name, age):
self.name = name
self.age = age
- 类属性访问: 实例对象或类对象都可以直接调用类属性,但实际建议使用类对象,因为使用实例对象会混淆;
# 实例化对象;
someone = Person("Nobody", 12)
# 分别通过实例对象和类对象调用类属性;
print("类属性为:", someone.type_) # 类属性为: Human
print("类属性为:", Person.type_) # 类属性为: Human
# 实例属性只能通过实例对象调用,不能使用类对象;
print("实例属性为:", "Name = ", someone.name, ", Age = ", someone.age) # 实例属性为: Name = Nobody , Age = 12
- 类属性修改: 类属性只能通过类对象修改,不能通过实例对象修改,因为后者表示为所属类创建一个实例属性;
print("类对象修改类属性前:", Person.type_) # 类对象修改类属性前: Human
Person.type_ = "人类" # 通过类对象修改类属性;
print("类对象修改类属性后:", Person.type_) # 类对象修改类属性后: 人类
someone = Person("Nobody", 12)
print("实例对象修改类属性前:", someone.type_) # 实例对象修改类属性前: Human
someone.type_ = "人类" # 通过实例对象修改类属性(本质是为所属类创建一个属性type_);
print("实例对象修改类属性后:", Person.type_) # 类对象修改类属性后: Human
print(someone.type_) # 人类
②类方法: 类中只使用了类属性的方法,第一个参数必须是类对象(默认cls
),且该方法的上方需要使用装饰器@classmethod
进行标识;
class Person(object):
type_ = "Human" # 类属性;
def __init__(self, name, age):
self.name = name
self.age = age
# 在类方法上方使用标识符@classmethod进行标识;
@classmethod
def demo(cls): # 第一个参数为cls,而不是self;
# 在类内部,只能通过类对象(cls)或类名,而不是实例对象(self)调用类属性;
print("大家好,我是%s!" % cls.type_)
print("大家好,我是%s!" % Person.type_)
- 类方法调用: 实例对象或类对象都可以直接调用类方法,但实际建议使用类对象,因为使用实例对象会混淆他人;
# 实例化对象;
someone = Person("Nobody", 12)
# 使用实例对象someone调用类方法;
someone.demo()
"""
大家好,我是Human!
大家好,我是Human!
"""
# 使用类对象Person调用类方法;
Person.demo() # Python解释器会自动将Person作为cls传给demo函数;
"""
大家好,我是Human!
大家好,我是Human!
"""
1.4.5 私有属性和私有方法
实际开发过程中,有时我们需要对象的某些属性或方法只能在类内部使用,而不被外部访问,此时就可以定义私有属性或方法;
- 需求: 定义一个银行账户类,具备账户名name和账户余额money,规定账户余额只能查询,不能充值;
class BankAccount(object):
def __init__(self, name, money):
self.name = name
self.money = money
# 实例化一个银行账户;
someone = BankAccount("小三", 100) # 初始100块;
print("银行卡账户余额:", someone.money) # 银行卡账户余额:100
someone.money = 100000
print("银行卡账户余额:", someone.money) # 银行卡账户余额:100000
- 分析: 可以看到,外部可以任意修改金额(money),很明显这与我们的需求相违背,所以我们需要将其设置为私有属性;
①私有属性或方法的设置: 在私有属性或方法前加上两个下划线__
,下文我们以私有属性为例进行说明,私有方法同理;
class BankAccount(object):
def __init__(self, name, money):
self.name = name
self.__money = money
def __demo(self):
print("")
# 实例化一个银行账户;
someone = BankAccount("小三", 100)
print("银行卡账户余额:", someone.money)
![私有属性不能访问](https://img-blog.csdnimg.cn/c80dc0967f8f4c66a01912d8dcaae322.png)
②私有属性或方法的访问: Python中,没有绝对的私有之说,只是访问的难易程度不同而已;
- 方式1: 通过
实例对象._类名__私有属性或方法
进行访问,并且基于此可以进行相应的修改;
# 实例化一个银行账户;
someone = BankAccount("小三", 100)
# 私有属性外部访问;
print("未修改前:", someone._BankAccount__money) # 未修改前: 100
# 私有属性外部修改;
someone._BankAccount__money = 500
print("私有属性外部修改后:", someone._BankAccount__money) # 私有属性外部修改后: 500
- 方式2: 通过在类内部为私有属性设置访问和修改方法,从而可以相对保证私有属性的安全性,这也是在实际中使用最为广泛的一种;
class BankAccount(object):
def __init__(self, name, money):
self.name = name
self.__money = money
# 定义访问私有属性money的方法,一般命名习惯为: 私有属性名_getter()或私有属性名_setter();
def money_getter(self):
return self.__money
# 定义修改私有属性money的方法;
def money_setter(self, new_money):
# 这种方法的实用性就在于可以对外部的赋值进行筛选;
if new_money > 10000:
print("请输入合法金额: 1-10000!")
return # 函数或方法中,如果return后无返回值代表结束函数或方法;
self.__money = new_money
# 实例化一个银行账户;
someone = BankAccount("小三", 100)
print("未修改前账户金额:", someone.money_getter()) # 未修改前账户金额: 100
someone.money_setter(1000)
print("合法修改账户金额:", someone.money_getter()) # 合法修改账户金额: 1000
someone.money_setter(20000) # 请输入合法金额: 1-10000!
print("非法修改账户金额:", someone.money_getter()) # 非法修改账户金额: 1000
- 方式3:
property
属性可以将类中的一个方法当作属性来使用,这样可以简化代码量,而它有分为基于装饰器和基于类属性两种方式;
"""
基于【装饰器】的property属性访问和修改;
"""
class BankAccount(object):
def __init__(self, name, money):
self.name = name
self.__money = money
# 获取私有属性: 获取属性时,执行下面装饰的方法;
@property
def money(self): # 方法名一定要与property装饰的属性名一致;
return self.__money
# 修改私有属性: 设置属性时,执行下面装饰的方法;
@money.setter
def money(self, new_money): # 方法名一定要与property装饰的属性名一致;
if new_money > 10000:
print("请输入合法金额: 1-10000!")
return
self.__money = new_money
# 实例化一个银行账户;
someone = BankAccount("小三", 100)
print("修改前账户金额:", someone.money) # 修改前账户金额: 100
someone.money = 5000
print("修改后账户余额:", someone.money) # 修改前账户金额: 5000
"""
基于【类属性】的property属性访问和修改,这种方式可以看作方式2的简化版;
"""
class BankAccount(object):
def __init__(self, name, money):
self.name = name
self.__money = money
def money_getter(self):
return self.__money
def money_setter(self, new_money):
if new_money > 10000:
print("请输入合法金额: 1-10000!")
return
self.__money = new_money
money = property(money_getter, money_setter) # 需要放在尾部,且变量名需要与私有属性名一致;
# 实例化一个银行账户;
someone = BankAccount("小三", 100)
print("修改前账户金额:", someone.money) # 修改前账户金额: 100
someone.money = 5000
print("修改后账户余额:", someone.money) # 修改前账户金额: 5000
1.4.6 静态方法
一个类中的方法如果既没有使用实例属性,也没有使用类属性,也就是说不需要形参self
或cls
,那么我们就称它为静态方法,一般使用装饰器@staticmethod
标识;
class Person(object):
type_ = "Human"
def __init__(self, name, age):
self.name = name
self.age = age
# 在静态方法上方使用标识符@classmethod进行标识;
@staticmethod
def greeting():
print("我是静态方法!")
- 静态方法调用: 静态方法既不需要实例属性,也不需要类属性,所以可直接通过类对象调用,减少不必要的内存占用和性能消耗;
# 实例化对象调用静态方法;
someone = Person("唐三", 16)
someone.greeting() # 我是静态方法!
# 直接使用类对象调用静态方法(建议);
Person("慕白", 15).greeting() # 我是静态方法!
1.4.7 内存申请
构造方法__new__(cls, *args, **kwargs)
,另一个魔法方法,它是我们创建对象时Python解释器调用的第一个方法,用于申请内存空间,它的第一个形参(cls
)代表当前类,其它不定长参数会在该方法成功调用后全部传递给__init__(self)
方法进行初始化。
①无返回值的__new__(cls, *args, **kwargs): 该方法如果没有返回值或返回值如果是None
,那么__init__(self)
方法不会被执行🤨;
class Demo(object):
def __init__(self):
print("实例化对象第二步!")
# 实例化对象: 如果子类不重写__new__()方法,那么Python解释器默认就会调用父类的该方法,创建并返回一个实例对象;
demo = Demo() # 实例化对象第二步!
print(demo) # <__main__.Demo object at 0x00000167F8599400>
class Demo(object):
def __new__(cls, *args, **kwargs):
print("实例化对象第一步!")
def __init__(self):
print("实例化对象第二步!")
# 实例化对象1: __new__()的功能就是返回类的实例对象,而__init__()初始化的前提就是对象已经存在,如果都没有生成对象,初始化个寂寞;
demo1 = Demo() # 实例化对象第一步!
print(demo1) # None --> 对象demo3无内存地址,说明它压根就没有创建成功!
# 这种情况下,我们也可以手动申请内存;
demo2 = object.__new__(Demo)
print(demo2) # <__main__.Demo object at 0x000002195E497580>
Demo.__init__(demo2) # 实例化对象第二步!
②有返回值的__new__(cls, *args, **kwargs): 返回值只能调用父类中的__new__()
方法,不能调用其它毫无关系的类中的该方法;
class Demo(object): # 继承自字符串str类;
# __new__()方法始终都属于类方法,即使它没有加上类方法装饰器(@classmethod)
def __new__(cls, *args, **kwargs): # __new__()的第一个参数为当前类对象,属于类级别的方法;
print("实例化对象第一步!")
first_step = object.__new__(cls)
print("first_step的内存地址为:", id(first_step))
return first_step # 创建并返回实例对象,使用临时变量对其进行保存,并传给__init__()方法;
def __init__(self, name): # __init__()第一个参数为实例对象,属于实例级别的方法;
print("实例化对象第二步!")
print("self的内存地址为:", id(self))
# 实例化对象;
demo3 = Demo("未命名")
print("实例化对象demo3的内存地址为:", id(demo3))
![实例化对象过程](https://img-blog.csdnimg.cn/becd157695094adca7ee3904d8b07418.png)
__new__()
方法可以返回当前类的实例对象,这个对象在内部又会传递给__init__()
方法中的self
,以便实例对象可以成功初始化;__init__()
方法中除了形参self
之外定义的其它参数,都需要与__new__()
方法中除形参cls
之外的参数保持一致;
③实际应用1: 单例设计模式,确保某个类只有一个实例,如电脑上只有一个回收站,它属于对象创建型模式的一种;
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 分别实例化两个对象: 这肯定是不行的,因为__new__()每次在实例化时都会调用,申请新的内存空间;
stu1 = Person("大明", 10000)
stu2 = Person("二明", 99999)
print("对象stu1的内存地址为:", id(stu1)) # 对象stu1的内存地址为: 2213843342336
print("对象stu2的内存地址为:", id(stu2)) # 对象stu2的内存地址为: 2213847066992
# 思路: 第一次__new__()实例化对象后,将其保存,后面不管调用几次只返回这一个对象,就可以实现单例模式;
class Person(object):
__instance = None # 定义一个私有类属性,用来保存__new__()返回的实例对象,用户类内部调用,外部不能直接修改和访问;
__is_first = True # 私有类属性,确保该类创建出的对象属性一致;
@classmethod
def __new__(cls, *args, **kwargs): # 如果不重写,会自动调用object的__new__()方法,生成多个实例对象;
if not cls.__instance:
cls.__instance = object.__new__(cls)
return cls.__instance
def __init__(self, name, age): # init中的形参self就是new返回的实例,init只是在其基础上进行初始化操作,init不需要返回值;
if self.__is_first: # 实例对象只能调用类属性;
self.name = name
self.age = age
self.__is_first = False # 实例对象不能修改类属性,它只是创建了一个对象属性__is_first;
# 分别实例化两个对象: 一个类只创建出一个实例;
stu1 = Person("大明", 10000) # 它先进去之后就确定了内存空间和属性值;
stu2 = Person("二明", 99999) # 初始化时它调用的是stu1创建的对象属性__is_first,并非我们定义的类属性__is_first,所以不会进入if语句;
print("对象stu1的内存地址为:", id(stu1)) # 对象stu1的内存地址为: 2521520698752
print("对象stu2的内存地址为:", id(stu2)) # 对象stu2的内存地址为: 2521520698752
print("stu1的姓名和年龄分别为:", stu1.name, ",", stu1.age) # stu1的姓名和年龄分别为: 大明 , 10000
print("stu2的姓名和年龄分别为:", stu2.name, ",", stu2.age) # stu2的姓名和年龄分别为: 大明 , 10000
④实际应用2: 记录一个类创建了多少对象,我们既可以使用__new__()
方法也可以借助__init__()
方法;
class Person(object):
__count = 0
def __init__(self, name, age):
Person.__count += 1
self.name = name
self.age =age
def get_count(self):
return "总共创建了{}次!".format(self.__count) # self可以获取类属性,不能修改;
# 实例化对象;
p1 = Person("莹", 25)
p2 = Person("猴", 26)
p3 = Person("苹", 27)
p3.get_count() # 总共创建了3次!
class Person(object):
__count = 0
@classmethod
def __new__(cls, *args, **kwargs):
cls.__count += 1
return object.__new__(Person)
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def get_count(cls):
return "该类总共被调用了{}次!".format(cls.__count)
# 实例化对象;
p7 = Person("莹", 26)
p8 = Person("栓", 26)
p9 = Person("宝", 26)
p9.get_count() # # 总共创建了3次!
1.4.8 内存销毁
目前我们已经对一个对象的创建、初始化和使用都或多或少的有所了解,接下来就是诸神黄昏了。最后,当一个对象被删除或程序执行完成后,Python解释器会自动调用__del__(self)
魔法方法,也称为析构函数,这也就意味着我们可以在该方法中写入需要退出前的操作,如保存并关闭打开的文件、断开连接的数据库等。
①自动调用__del__(self): 程序正常执行完成后,Python解释器自动调用__del__()
魔法方法删除对象(引用计数为0);
class Washer(object):
def __new__(cls, *args, **kwargs):
print("对象实例化已完成!")
return object.__new__(cls)
def __init__(self):
print("对象初始化已完成!")
def __del__(self):
print("对象已经被删除!")
# 主程序入口;
if __name__ == '__main__':
haier = Washer()
"""
对象实例化已完成!
对象初始化已完成!
对象已经被删除!
"""
import time # 导入Python标准库中的time模块;
class Washer(object):
def __new__(cls, *args, **kwargs):
print("对象实例化已完成!")
return object.__new__(cls)
def __init__(self):
print("对象初始化已完成!")
def __del__(self):
print("对象已经被删除!")
# 主程序入口;
if __name__ == '__main__':
haier = Washer()
print("程序休眠2秒钟...")
time.sleep(2) # 程序休眠2秒钟后结束,然后Python解释器才调用__del__()删除对象;
"""
对象实例化已完成!
对象初始化已完成!
程序休眠2秒钟...
对象已经被删除!
"""
②手动调用__del__(self): 我们可以在程序执行期间,手动调用del
方法删除指定对象;
import time # 导入time模块;
class Washer(object):
def __new__(cls, *args, **kwargs):
print("对象实例化已完成!")
return object.__new__(cls)
def __init__(self):
print("对象初始化已完成!")
def __del__(self):
print("对象已经被删除!")
# 主程序入口;
if __name__ == '__main__':
haier = Washer()
del haier # 调用对象的__del__()方法删除该对象,程序休眠2秒钟后退出;
time.sleep(2)
print("程序需要休眠2秒钟...")
"""
对象实例化已完成!
对象初始化已完成!
对象已经被删除!
程序需要休眠2秒钟...
"""
- 注意: 手动调用
del
删除某个对象时,只有该对象的引用计数为0才能彻底将其删除;
import time # 导入time模块;
class Washer(object):
def __new__(cls, *args, **kwargs):
print("对象实例化已完成!")
return object.__new__(cls)
def __init__(self):
print("对象初始化已完成!")
def __del__(self):
print("对象已经被删除!")
# 主程序入口;
if __name__ == '__main__':
haier = Washer()
haier1 = haier # Python中一切皆引用,通过赋值操作,对象haier1和对象haier现在都指向同一块内存空间(存储着Washer实例);
del haier # 第一次调用del删除对象haier只是断掉了它与内存空间的连线,此时还有对象haier1连接这该空间;
time.sleep(2) # 程序休眠2秒钟后退出的时候才调用del 删除对象haier1;
print("程序需要休眠2秒钟...")
"""
对象实例化已完成!
对象初始化已完成!
程序需要休眠2秒钟...
对象已经被删除!
"""
![两个对象指向同一块内存空间](https://img-blog.csdnimg.cn/5a6c01cef2fd417480dfd94a59f41ce8.png)
from copy import copy
import time # 导入time模块;
class Washer(object):
def __new__(cls, *args, **kwargs):
print("对象实例化已完成!")
return object.__new__(cls)
def __init__(self):
print("对象初始化已完成!")
def __del__(self):
print("对象已经被删除!")
if __name__ == '__main__':
haier = Washer()
haier1 = copy(haier) # copy()方法会将对象haier的引用及其指向的内存空间全部复制一份给对象haier1;
del haier # 调用del方法删除对象haier(它们现在互不影响);
time.sleep(2) # 程序休眠2秒钟后退出的时候调用del删除对象haier1;
print("程序需要休眠2秒钟...")
"""
对象实例化已完成!
对象初始化已完成!
对象实例化已完成!
对象已经被删除!
程序需要休眠2秒钟...
对象已经被删除!
"""
![复制对象引用和内存空间](https://img-blog.csdnimg.cn/562d671928374fae9d0bba715c71b564.png)
1.4.9 将对象作为字典使用
通过前面的示例,我们已经知道,可以通过对象名.属性名=属性值
的方式为对象添加属性或对同名属性的值进行替换,而且我们也学习过字典可以通过变量名[key]=value
的方式添加新的元素或对同名key
的值进行覆盖。那么,我们是否可以将它们两者结合一下,以字典的形式为对象添加或访问属性?
# 定义一个字典,里面存储了每个人的姓名及年龄,其中姓名作为字典的key,年龄作为字典的value;
name_list = {"Jack": 25, "Rose": 20, "Lily": 25}
print(name_list) # {'Jack': 25, 'Rose': 20, 'Lily': 25}
# 为字典添加新元素;
name_list["云韵"] = 20
name_list["小舞"] = 25
print(name_list) # {'Jack': 25, 'Rose': 20, 'Lily': 25, '云韵': 20, '小舞': 25}
# 从字典中删除指定键值对;
print(name_list.pop("Rose"))
print(name_list) # {'Jack': 25, 'Lily': 25, '云韵': 20, '小舞': 25}
class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 实例化对象;
animal = Animal("旺财", 25)
# print("对象animal的属性为:", animal.name, ",", animal.age) # 对象animal的属性为: 旺财, 25
# 为对象添加属性;
animal["age"] = 20 # 报错信息如下
![以字典的形式为对象添加属性](https://img-blog.csdnimg.cn/b72158c2da6b483b92f783cdc5ed969c.png)
①__dict__: Python内置属性,它可以将对象的属性以字典的形式返回,其中属性名为字典的key
,属性值为字典的value
;
class Figure(object):
def __init__(self, name, age, color):
self.name = name
self.age = age
self.color = color
# 主程序入口;
if __name__ == '__main__':
# 实例化对象;
girl = Figure("云韵", 25, "黑色")
print(girl.__dict__) # {'name': '云韵', 'age': 25, 'color': '黑色'}
- 注意: 如果对象所属类中使用了
__slots__
对属性进行了限制,此时再调用对象的__dict__
返回对象属性会报错;
class Figure(object):
__slots__ = ("name", "age", "color") # 属性限制;
def __init__(self, name, age, color):
self.name = name
self.age = age
self.color = color
# 主程序入口;
if __name__ == '__main__':
# 实例化对象;
girl = Figure("云韵", 25, "黑色")
print(girl.__dict__) # 使用__slots__对对象属性限制后,此时调用对象的__dict__会报错;
![__slots__属性限制导致__dict__报错](https://img-blog.csdnimg.cn/6c7d123d5e6a436ca9f683fcfe6b13f3.png)
②以字典的形式访问和设置对象属性: 魔法方法__setitem__()
和__getitem__()
可以分别添加和获取对象属性,而__delitem__()
则可以删除对象属性;
class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 为对象添加属性;
def __setitem__(self, key, value):
self.__dict__[key] = value
# 访问或获取对象属性;
def __getitem__(self, item):
return self.__dict__[item]
# 删除对象属性;
def __delitem__(self, key):
self.__dict__.pop(key)
# 主程序入口;
if __name__ == '__main__':
animal = Animal("旺财", 25)
print("对象animal原始属性为:{}, {}".format(animal.name, animal.age)) # 对象animal原始属性为:旺财, 25
# 修改对象属性值;
animal["name"] = "八公"
# 为对象添加新属性;
animal["species"] = "田园犬"
animal["spouse"] = False
# 查看对象指定属性或全部属性;
print(animal["name"]) # 八公
print(animal.__dict__) # {'name': '八公', 'age': 25, 'species': '田园犬', 'spouse': False}
# 删除对象指定属性;
del animal["spouse"] # 自动触发对象的__delitem__()魔法方法;
print(animal.__dict__) # {'name': '八公', 'age': 25, 'species': '田园犬'}
1.5 其它魔法方法
这个部分的魔法方法在实际中使用并不如上面介绍的那几种方法频繁,以下仅仅只是为了拓展知识面,完善知识体系,所以并不会进行过于详细地解释,感兴趣的可以继续阅读。本质上,前面乃至后面的魔法方法也被称为运算符重载,指的是在类中定义并实现一个与运算符对应的处理方法,这样当类对象在进行运算符操作时,系统就会调用类中相应的方法来处理。
1.5.1 比较运算符相关
比较运算符==
默认会比较两个对象的内存地址,对于自定义对象也可以通过重写对象的__eq__()
方法自定义比较规则。同理,如果我们需要使用其它比较运算符,如>、<、!=、≤、≥
等,对自定义对象进行比较,是否可以照猫画虎,您还别说,Python还真有这个功能。
class Person2(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other): # self: 指向当前调用对象
return (self.name == other.name) and (self.age == other.age) # 返回值是一个布尔值Boolean(True, False)
def __ne__(self, other): # (不等于not equal)使用 != 运算符会自动调用该方法
return not (self.__eq__(other))
def __gt__(self, other): # (大于great than)使用 > 运算符会自动调用该方法
return self.age > other.age
def __ge__(self, other): # (大于等于great equal)使用 >= 运算符会自动调用该方法
return self.age >= other.age
def __lt__(self, other): # (小于less than)使用 < 运算符会自动调用该方法
return self.age < other.age
def __gt__(self, other): # (小于等于less equal)使用 <= 运算符会自动调用该方法
return self.age <= other.age
# 主程序入口
if __name__ == '__main__':
P1 = Person2("张三", 25)
P2 = Person2("张三", 25)
P3 = Person2("李四", 26)
print("对象p1和对象p2是否相等:", P1 == P2) # 对象p1和对象p2是否相等: True
print("对象p1和对象p3是否相等:", P1 == P3) # 对象p1和对象p3是否相等: False
print("对象p1和对象p2是否相等:", P1 is P2) # 对象p1和对象p2是否相等: False
print("对象P1和对象P2不相等:", P1 != P2) # 对象P1和对象P2不相等: False
print("对象P1是否大于对象P3:", P1 > P3) # 对象P1是否大于对象P3: True
print("对象P1是否大于等于对象P3:", P1 >= P3) # 对象P1是否大于等于对象P3: False
print("对象P1是否小于对象P3:", P1 < P3) # 对象P1是否小于对象P3: True
print("对象P1是否小于等于对象P3:", P1 <= P3) # 对象P1是否小于等于对象P3: True
1.5.2 算术运算符相关
除了比较运算符,Python还支持我们重写各种算术运算符,如+、-、*、/、//、%
等,以对自定义对象的功能进行扩展。
class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __add__(self, other): # 加(+)
return self.age + other
def __sub__(self, other): # 减(-)
return self.age - other
def __mul__(self, other): # 乘(*)
return self.age * other
def __truediv__(self, other): # 整除(/)【区别于取模(整) //(向下取整)】
return self.age / other
def __mod__(self, other): # 取余(%)
return self.age % other
def __pow__(self, other): # 幂(**)
return self.age ** other
# 主程序入口;
if __name__ == '__main__':
stu = Student("张三", 25)
print(stu + 2) # 27
print(stu - 2) # 23
print(stu * 2) # 50
print(stu / 2) # 12.5
print(stu % 2) # 1
print(stu ** 2) # 625
# 补充: 取整及取余;
integer, remainder = divmod(10, 3)
print("10除3的整数是{}, 余数是{}!".format(integer, remainder)) # 10除3的整数是3, 余数是1!
1.5.3 类型转换相关
Python基础变量类型,如int、str、float
等之间在符合规则的条件下可相互转换,自定义对象是否也支持这种操作呢?
class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __int__(self):
return self.age
def __float__(self):
return self.age * 1.0
def __bool__(self):
return self.age > 18
def __str__(self):
return "我是" + self.name
# 主程序入口;
if __name__ == '__main__':
animal = Animal("猴子", 3)
print(animal) # 我是猴子
print(int(animal)) # 3
print(float(animal)) # 3.0
print(bool(animal)) # False
print(str(animal)) # 我是猴子
二、继承
在面向对象编程中,继承是一种设计类的技巧,主要是让类与类之间产生从属关系,并且子类默认继承父类所有的属性和方法,这样一来可以大大提高代码的复用性和简洁性。此外,在Python中,子类(派生类)可以同时继承自一个或多个父类(基类或顶级类),所以Python中的继承又可分为单继承、多继承和多层继承。
现实生活中的继承:
![生活中的继承](https://img-blog.csdnimg.cn/69b6e95df8c74199b1eb62f74560e9e6.png)
2.1 单继承
一般格式: 一个子类只能继承自一个父类,且默认它会继承该父类所有的属性和方法;
class 子类名(父类): # 父类或基类一般在【类名】后面使用圆括号()括起来即可;
"""
Do something!
"""
pass
故事主线: 一个煎饼果子老师傅,在业界摸爬滚打多年,总结出一套精湛地摊煎饼技术,想要传给它最得意的一个弟子;
# 先创建一个师傅类
class Master(object):
def __init__(self):
self.kong_fu = "【古法煎饼果子配方】"
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
# 定义一个徒弟类,继承自师傅类,子类默认继承父类的所有属性和方法;
class Prentice(Master):
# 子类也可以拥有属于自己的方法: daqiu经过市场调研发现单单只销售饼子不能满足客户需求,于是他又卖起了奶茶;
def make_drink(self):
print("daqiu独创奶茶!")
# 实例化一个弟子对象daqiu,调用师傅的属性和方法;
daqiu = Prentice()
print(daqiu.kong_fu) # 【古法煎饼果子配方】
daqiu.make_cake() # 使用【古法煎饼果子配方】制作煎饼果子!
daqiu.make_drink() # daqiu独创奶茶!
- 注意: 子类并没有直接继承父类的私有属性和方法,不过正如我们在
1.4.5
小节所讲,不存在绝对的私有,只是访问难度的问题;
class Master(object):
def __init__(self):
self.kong_fu = "【古法煎饼果子配方】"
self.__money = 100000 # 师傅的钱为私有属性,外部以及子类不能直接使用;
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
class Prentice(Master):
# 子类也可以拥有属于自己的方法: daqiu经过市场调研发现单单只销售饼子不能满足客户需求,于是他又卖起了奶茶;
def make_drink(self):
print("daqiu独创奶茶!")
# 主程序入口;
if __name__ == '__main__':
# 实例化对象;
daqiu = Prentice()
print("师傅总共有{}元!".format(daqiu.money))
![子类不能直接继承父类私有属性](https://img-blog.csdnimg.cn/65d8e54748b646f78b2de26d60a5e18a.png)
# 1.4.5给出了私有属性的详细介绍,包括它的设置和访问,我们这里采用为外部设置私有属性的访问和修改方法;
class Master(object):
def __init__(self):
self.kong_fu = "【古法煎饼果子配方】"
self.__money = 100000
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
# 为外部设置私有属性访问方法;
def money_getter(self):
return self.__money
# 外部设置私有属性修改方法;
def money_setter(self, new_money):
if new_money < self.__money: # 加个判断;
print("为师的钱你也敢动!")
return
self.__money = new_money
class Prentice(Master):
# 子类也可以拥有属于自己的方法: daqiu经过市场调研发现单单只销售饼子不能满足客户需求,于是他又卖起了奶茶;
def make_drink(self):
print("daqiu独创奶茶!")
# 主程序入口;
if __name__ == '__main__':
# 实例化对象;
daqiu = Prentice()
print("师傅总共有:%s元!" % daqiu.money_getter()) # 师傅总共有:100000元!
# 有孝心的daqiu为师傅又存了100000元!
daqiu.money_setter(200000)
print("师傅总共有:%s元!" % daqiu.money_getter()) # 师傅总共有:200000元!
2.2 多继承
一般格式: 一个子类可以同时继承自多个父类,且默认会拥有所有父类的属性和方法;
class 子类名(父类1, 父类2, ..., 父类n): # 父类或基类一般在【类名】后面使用圆括号()括起来即可;
"""
Do something!
"""
pass
故事情节: daqiu是一个爱学习的好孩子,学习之余想学习更多的煎饼果子技术,所以找到黑马程序员报班学习技术;
# 直接从上面拿来师傅类;
class Master(object):
def __init__(self):
self.kong_fu = "【古法煎饼果子配方】"
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
class School(object):
def __init__(self):
self.kong_fu = "【黑马煎饼果子配方】"
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
思考: 多继承使得子类拥有所有父类的属性和方法,但不同父类拥有相同方法,子类调用该方法时,会先调用哪个父类的方法?
①子类daqiu先继承Master,然后再继承School;
# 构建一个弟子类,并实例化对象daqiu;
class Prentice(Master, School):
"""
Do something!
"""
pass
daqiu = Prentice()
print(daqiu.kong_fu) # 【古法煎饼果子配方】
daqiu.make_cake() # 使用【古法煎饼果子配方】制作煎饼果子!
②子类daqiu先继承School,然后再继承Master;
# 构建一个弟子类,并实例化对象daqiu;
class Prentice(School, Master):
"""
Do something!
"""
pass
daqiu = Prentice()
print(daqiu.kong_fu) # 【黑马煎饼果子配方】
daqiu.make_cake() # 使用【黑马煎饼果子配方】制作煎饼果子!
结论: 子类调用不同父类的同名属性或方法时,默认使用第一个父类的属性和方法;
通过改变子类继承父类的顺序,分别比较它们的执行结果,我们暂时得出了上述看上去还能接受的结论。然而,由于我们无法穷举所有的可能情况,所以这样的结果总是让人无法信服。因此,实际开发中我们一般会借助MRO(Method Resolution Order)
来判断多继承中子类调用父类同名属性或方法的顺序,并且Python解释器会按照该顺序对属性或方法进行调用,如果找到则执行,否则程序就会报错。注意: 该功能需要使用继承多个父类的【子类名】而非【子类实例对象】调用,即子类名.__mro__
。即使如此,实际工作中若多个父类具有同名属性或方法,应该尽量避免使用多继承。
# 子类先继承Master,再继承School
print(Prentice.__mro__) # 以元组的形式返回结果;
# (<class '__main__.Prentice'>, <class '__main__.Master'>, <class '__main__.School'>, <class 'object'>)
# 子类先继承School,再继承Master
print(Prentice.__mro__) # 以元组的形式返回结果;
# (<class '__main__.Prentice'>, <class '__main__.School'>, <class '__main__.Master'>, <class 'object'>)
2.3 多层继承
一般格式: 子类继承自多个父类,而次类又继承自多个子类…
class 子类(父类1, 父类2, ..., 父类n):
"""
Do something!
"""
pass
class 次类(子类1, 子类2, ..., 子类n):
"""
Do something!
"""
pass
......
故事情节:
- Animal类为所有动物的父类,拥有name和age属性,方法为eat、drink、run和sleep;
- Dog类和Cat类继承自Animal类,在父类的基础上增加了variety属性,方法分别增加了bark和catch;
- XiaoTianQuan类集成自Dog类,增加了fly方法,属性不变;
![多继承示例](https://img-blog.csdnimg.cn/6a48a3a027284488a011379ebb0c3509.png)
# 先构造一个Animal类
class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print("我是%s,我正在吃饭!" % self.name)
def drink(self):
print("我是%s,我正在喝水!" % self.name)
def run(self):
print("我是%s,我正在奔跑!" % self.name)
def sleep(self):
print("我是%s,我正在睡觉!" % self.name)
# 然后再分别定义Dog类和Cat类,它们继承自Animal类;
class Dog(Animal):
def __init__(self, name, age, variety):
super().__init__(name, age) # 调用父类方法(随后2.5详解)
self.variety = variety
def bark(self):
print(f"我是{self.name},我正在狂吠!")
class Cat(Animal):
def __init__(self, name, age, variety):
super(Cat, self).__init__(name, age) # 调用父类方法(随后2.5详解)
self.variety = variety
def catch(self):
print(f"我是{self.name},我正在爬树!")
# 最后定义XiaoTianQuan类,它又继承自Dog类;
class XiaoTianQuan(Dog):
def fly(self):
print("我是{},我正在和二郎神在飞!".format(self.name))
if __name__ == '__main__':
# 实例化对象,并调用它内部的方法;
xiao_tian_quan = XiaoTianQuan("哮天犬", 25, "二郎神坐骑")
xiao_tian_quan.eat()
xiao_tian_quan.drink()
xiao_tian_quan.run()
xiao_tian_quan.sleep()
xiao_tian_quan.bark()
xiao_tian_quan.fly()
程序执行结果:
2.4 子类【重写】父类属性或方法
故事情节: daqiu掌握了师傅和黑马的技术后,自己钻研出一套全新的、独门配方的煎饼果子技术;
# 直接拿来师傅类;
class Master(object):
def __init__(self):
self.kong_fu = "【古法煎饼果子配方】"
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
# 直接拿来黑马类;
class School(object):
def __init__(self):
self.kong_fu = "【黑马煎饼果子配方】"
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
# 构建弟子类;
class Prentice(object):
# 重写__init__()
def __init__(self):
self.kong_fu = "【Daqiu独创煎饼果子配方】"
# 重写make_cake()
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
# 实例化一个弟子对象Daqiu;
daqiu = Prentice()
print(daqiu.kong_fu) # 【Daqiu独创煎饼果子配方】
daqiu.make_cake() # 使用【Daqiu独创煎饼果子配方】制作煎饼果子!
结论: 子类和父类拥有同名的属性或方法,优先使用子类的同名属性或方法;
2.5 子类【调用】父类属性或方法
①引例: 实际开发中,子类更多的是在父类属性或方法的基础上添加功能,那么此时就涉及如何调用父类的属性或方法;
# 先定义一个Person类,它具有name和age属性且具备eat()方法;
class Person(object):
def __init__(self, name, age):
self.name = name
self. age = age
def eat(self):
print("我是%s,今年%d岁了,我正在吃火锅!" % (self.name, self.age))
1. 使用super(当前类名, self).属性或方法
进行调用:
# 学生类在继承Person类的基础上增加school属性,并且在父类eat方法的基础上增加打印school信息;
class Student(Person):
def __init__(self, name, age, school):
super(Student, self).__init__(name, age) # super()内的第一个参数为当前类名,第二个参数为当前类实例对象;
self.school = school
def eat(self):
super(Student, self).eat() # super()内的第一个参数为当前类名;
print("我现在位于%s!" % self.school)
def __str__(self):
return "name = %s, age = %d, school = %s" % (self.name, self.age, self.school)
# 实例化一个学生对象;
stu = Student("丽娘", 99, "思量岛")
print(stu) # name = 丽娘, age = 99, school = 思量岛
stu.eat() # 程序执行结果如下;
"""
我是丽娘,今年99岁了,我正在吃火锅!
我现在位于思量岛!
"""
2. 使用super().属性或方法
进行调用:
# ITCoder类在继承Person类的基础上增加hair_style属性,并且重写eat方法;
class ITCoder(Person):
def __init__(self, name, age, hair_style):
super().__init__(name, age) # super()内不需要传递任何参数,它会根据继承顺序自动调用相应的方法;
self.hair_style = hair_style
def eat(self):
print("大家好,我是一名代码狗!")
super().eat() # super()内不需要传递任何参数,它会根据继承顺序自动调用相应的方法;
def __str__(self):
return "name = %s, age = %d, hair_style = %s" % (self.name, self.age, self.hair_style)
# 实例化一个程序员对象;
coder = ITCoder("小菜", 25, "秃子")
print(coder) # name = 丽娘, age = 99, hair_style = 秃子
coder.eat() # 程序执行结果如下
"""
大家好,我是一名代码狗!
我是小菜,今年25岁了,我正在吃火锅!
"""
3. 使用父类名.属性或方法
进行调用:
class CivilServant(Person):
def __init__(self, name, age, slogan):
Person.__init__(self, name, age) # 通过【父类名】.方法(self, *args, **kwargs)调用;
self.slogan = slogan
def eat(self):
print("大家好,我是一名公务人员!")
Person.eat(self) # 通过【父类名】.方法(self)调用;
def __str__(self):
return "name = %s, age = %d, slogan = %s" % (self.name, self.age, self.slogan)
# 实例化一个公务员对象;
gov = CivilServant("小白", 25, "为人民服务")
print(gov) # name = 小白, age = 25, slogan = 为人民服务
gov.eat() # 程序执行结果如下;
"""
大家好,我是一名公务人员!
我是小白,今年25岁了,我正在吃火锅!
"""
总结:
父类名.属性或方法
,如果父类名发生改变,那么后期代码的维护将会非常困难,所以尽量不要使用这种方式;super(当前类名, self)
或super()
的作用一致,但是如果当前类名发生变化,维护也是较为麻烦,所以建议使用后者。但是,它们都不太适合于多继承中父类属性或方法同名情况,因为它们的调用顺序取决于MRO
中的顺序,这样一来难以精准调用;
②情景再现: 如果此时很多顾客希望既能吃到daqiu的独创煎饼果子,又能吃到Master或School的煎饼果子,如何实现?`
# 直接拿来Master类和School类;
class Master(object):
def __init__(self):
self.kong_fu = "【古法煎饼果子配方】"
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
class School(object):
def __init__(self):
self.kong_fu = "【黑马煎饼果子配方】"
def make_cake(self):
print(f"使用{self.kong_fu}制作煎饼果子!")
# 定义弟子类;
class Prentice(Master, School):
def __init__(self):
self.kong_fu = "【独创煎饼果子配方】"
def make_cake(self):
print(f"运用{self.kong_fu}制作煎饼果子!")
def make_master_cake(self):
super().__init__()
super().make_cake()
def make_school_cake(self):
School.__init__(self) # 把当前类实例对象对象传进去;
School.make_cake(self)
# 实例化一个弟子对象;
daqiu = Prentice()
daqiu.make_cake()
daqiu.make_master_cake()
daqiu.make_school_cake()
程序执行结果:
![](https://img-blog.csdnimg.cn/3f560c125d9e42a69834b0e7622f84d2.png)
思考: 子类中定义的代码是否有问题?
# 改变上述daqiu调用方法的顺序: 先调用mater,再调用自身,然后调用school;
daqiu = Prentice()
daqiu.make_master_cake()
daqiu.make_cake()
daqiu.make_school_cake()
程序执行结果: 可以看到,先调用Master方法后,它的__init__()
覆盖了子类daqiu中的相应属性
解决方案: 子类调用自己的方法前,先调用自己的__init__
进行初始化,否则会被父类的属性所覆盖;
class Prentice(Master, School):
# 代码省略部分同上,此处只为突出修改的重点;
def make_cake(self):
# 在调用属性之前,先调用自己子类的初始化,否则会被父类的属性覆盖;
self.__init__()
print(f"运用{self.kong_fu}制作煎饼果子!")
daqiu = Prentice()
daqiu.make_master_cake()
daqiu.make_cake()
daqiu.make_school_cake()
修改后程序执行结果: 显然,修改之后的代码健壮性更强了,也更准确了;
2.6 判断对象的类型
在Python中,经常会使用内置函数type()
和isinstance()
来判断一个变量或对象属于哪种类型,那么它们有什么不同之处呢?type()
接受一个对象object
,判断并返回它的类型,但认为子类实例对象与父类类型不同,而isinstance()
则可以判断一个实例对象是否由子类或它继承的父类创建而成,但不能得到它的类型。
①常见Python数据类型: 使用type()
和isinstance()
都可以准确实现这个需求;
# 通过内置函数type(object)判断: 它会判断并返回传入object所属类型,所以我们可以通过它的返回值直接进行判断;
print(type(10) == int) # True
print(type("Hello") == str) # True
print(type(85.36) == float) # True
print(type(1+2j) == complex) # True
print(type([10, 20, 30]) == list) # True
print(type((10,)) == tuple) # True
print(type({"name": "Jack", "age": 20}) == dict) # True
print(type({"name", "age", "hobby"}) == set) # True
print(type({}) == set) # False: Python中,空集合只能使用set()进行表示,{}表示空字典;
print(type({}) == dict) # True
# isinstance(object, type)根据传入的object和type进行判断,如果object属于type则返回True,否则返回False;
print(isinstance(10, int)) # True
print(isinstance("Hello", str)) # True
print(isinstance(85.26, float)) # True
print(isinstance(10+2j, complex)) # True
print(isinstance([10, 20, 30], list)) # True
print(isinstance((10,), tuple)) # True
print(isinstance({"name": "Jack", "age": 20}, dict)) # True
print(isinstance({"name", "age", "hobby"}, set)) # True
print(isinstance({}, set)) # False: Python中,空集合只能使用set()进行表示,{}表示空字典;
print(isinstance({}, dict)) # True
②自定义对象比较: isinstance()
可以判断一个子类的实例对象是否属于它的父类,而type()
则不具备此能力;
# 自定义一个Animal动物类;
class Animal(object):
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print("大家好,我是%s,今年%d岁了,我正在奔跑!" % (self.name, self.age))
# 定义一个Dog子类,它继承自Animal类;
class Dog(Animal):
def __init__(self, name, age, variety):
super(Dog, self).__init__(name, age)
self.variety = variety
# 分别实例化一个Animal对象和Dog对象;
animal = Animal("万物生", 100)
dog = Dog("八公", 12, "柯基")
# 使用type()进行判断;
print(type(animal) == Animal) # True
print(type(dog) == Dog) # True
print(type(dog) == Animal) # False: 子类实例对象dog不属于它的父类Animal;
# 通过对象.__class__可以查看当前操作的对象是哪个类,功能上与type()等价;
print(dog.__class__ == Dog) # True
# 使用isinstance()进行判断;
print(isinstance(animal, Animal)) # True
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True: 子类实例对象dog属于它的父类Animal;
③补充: isubclass()
可以判断两个类之间的继承关系;
# 增加一个学生类Student,它继承自基类object;
class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age
# 判断Animal、Dog和Student类之间的关系;
print(issubclass(Animal, object)) # True
print(issubclass(Dog, Animal)) # True
print(issubclass(Dog, object)) # True
print(issubclass(Student, object)) # True
print(issubclass(Student, Animal)) # False
三、抽象类与多态
在Python中,抽象类与多态解决问题的思想很相似,了解、掌握它们的不同以及相同之处对于提高代码的简洁性、可读性和扩展性至关重要。
3.1 抽象类
目前为止,我们所接触到的所有类都属于普通类,它们可以直接产生实例化对象,并且在这个类中可以包含构造方法、实例方法、类方法、静态方法、和属性等内容。相反,抽象类则是在普通类中增加了抽象方法,而抽象方法指的是只在类中进行定义,但内部无任何具体实现的方法。不像其它静态语言,如Java,可以通过关键字abstract
直接实现抽象类,而Python中则需要通过从abc
模块中导入元类(ABCMeta
)——所有抽象类的基类,同时该模块中还包括抽象方法(abstractmethod
)和抽象属性(abstractproperty
)。因此,根据抽象类的定义,它具有以下几个特点:
- 抽象类中可以包含正常方法和抽象方法,其中抽象方法需要使用装饰器
@abstractmethod
来标识,且它的里面无任何可实现的代码; - 抽象类只能被继承,不能被实例化,只有继承且实现了它所有抽象方法的子类才能被实例化;
- 抽象类中可以添加成员属性和抽象属性(
abstractproperty
),且一个类中只要包含抽象方法或抽象属性之一,就不能被实例化;
①判断以下类是否属于抽象类:
from abc import ABCMeta # 从Python内置模块中导入ABCMeta;
class TestClass(metaclass=ABCMeta):
pass
cls = TestClass() # 可以实例化;
from abc import ABCMeta, abstractmethod # 从abc模块中导入 abstractmethod;
class AbstractClass(metaclass=ABCMeta):
# 一般,装饰器@abstractmethod下方的方法即为抽象方法;
@abstractmethod
def abstract_method(self):
pass # 抽象方法不能包含任何可实现代码;
demo = AbstractClass() # 不能被实例化,因为它内部有抽象方法;
②举个栗子: 每个人都具备吃喝玩乐的功能,但是具体娱乐方式因人而异,这也就意味着我们在定义一个Person类时不能将这些功能写死;
from abc import ABCMeta, abstractmethod, abstractproperty
# 定义一个包含两个抽象方法的抽象类;
class Person(metaclass=ABCMeta):
def __init__(self, name, age):
self.name = name
self.age = age
self.school = "史莱克学院"
# 抽象类中可以包含实例方法;
def run(self):
print("我是%s,我正在跑步!" % self.name)
@abstractmethod
def eat(self):
pass
@abstractmethod
def play(self):
pass
# 定义一个【子类1】:它必须实现父类的所有抽象方法;
class Subclass(Person): # Pycharm中 Ctrl+I 可以自动实现(implement)抽象方法;
def eat(self):
print("我是%s,我正在星斗大森林吃火锅!" % self.name)
def play(self):
print("我是%s,我正在和大明、二明在玩耍!" % self.name)
per = Subclass(name="小舞", age=25)
per.eat()
# 程序执行结果为: 我是小舞,我正在星斗大森林吃火锅!
per.play()
# 程序执行结果为: 我是小舞,我正在和大明、二明在玩耍!
# 定义一个【子类2】:它必须实现父类的所有抽象方法;
class Subclass(Person): # Pycharm中 Ctrl+I 可以自动实现(implement)抽象方法;
def eat(self):
print("我是%s,我正在%s卖香肠!" % (self.name, self.school))
def play(self):
print("我是%s,我正在七宝琉璃宗追宁荣荣!" % self.name)
per2 = Subclass(name="奥斯卡", age=26)
per2.eat()
# 程序执行结果为: 我是奥斯卡,我正在史莱克学院卖香肠!
per2.play()
# 程序执行结果为: 我是奥斯卡,我正在七宝琉璃宗追宁荣荣!
3.2 多态
多态指的是一类事物具有多种形态,常用于静态语言,如Java。多态可以让具有不同功能的函数使用相同的函数名,这样就可以通过一个函数名调用不同功能的函数,产生不同的执行结果。此外,多态的实现是以继承(非强制,但建议)和重写父类方法为前提,所以它只是调用方法的一种技巧,并不会影响到类内部的设计。因此,这种方式可以增加外部调用的灵活性,兼容性更强,可以更好地适应不断变化的需求。
编程实现: 缉毒犬
用于追查毒品,军犬
负责追击敌人,导盲犬
用于领路,人
让不同的狗干不同的事;
方案1: 不使用多态实现;
![不使用多态实现需求](https://img-blog.csdnimg.cn/a30ea5cdd92e48409dfdf6f12d7db4f4.png)
"""
分别定义缉毒犬、军犬和导盲犬类及其对应的方法;
"""
class DrugDog(object):
def search_drug(self):
print("缉毒犬正在搜索毒品!")
class PoliceDog(object):
def attack_enemies(self):
print("军犬正在追击敌人!")
class BlindDog(object):
def load_road(self):
print("导盲犬正在领路!")
# 定义Person类,具有姓名(name)和狗品种(dog)属性,不同的狗执行不同的任务;
class Person(object):
def __init__(self, name):
self.name = name
self.dog = None
def work_with_drug(self): # 让缉毒犬工作;
if self.dog:
self.dog.search_drug()
def work_with_police(self): # 让军犬工作;
if self.dog:
self.dog.attack_enemies()
def work_with_blind(self): # 让导盲犬工作;
if self.dog:
self.dog.load_road()
# 对上述定义的各种类进行实例化,并按照相应逻辑执行代码;
someone = Person("唐三")
drug_dog = DrugDog()
someone.dog = drug_dog # someone.dog --> drug_dog --> 调用所属类内部的方法;
someone.work_with_drug()
# 程序执行结果: 缉毒犬正在搜索毒品!
police_dog = PoliceDog()
someone.dog = police_dog
someone.work_with_police()
# 程序执行结果: 军犬正在追击敌人!
blind_dog = BlindDog()
someone.dog = blind_dog
someone.work_with_blind()
# 程序执行结果: 导盲犬正在领路!
目前看来,不使用多态也可以实现这个需求。Ok,现在假如又多了一个犬种,那么此时我们就需要在Person
类中定义一个与之对应的方法,通过这个方法操纵这条狗。显然,Person
类会一直在不停地添加新的功能,每次都需要改动它内部的代码,程序扩展性太差,不符合开放封闭原则(功能开放,代码封闭)。
方案2: 使用多态实现这个需求,它的实施主要可分别以下三步。
- 定义父类,并提供公共方法;
- 定义子类,继承并重写父类方法;
- 传递子类对象给调用者,可以看到不同的子类实现效果不同;
![](https://img-blog.csdnimg.cn/e3d7a575b43e42c3b394d7159eeef9d6.png)
1.定义一个父类,提供公共方法;
class Dog(object):
def work(self):
pass
2.定义子类,继承并实现父类方法;
class DrugDog(Dog):
def work(self):
print("缉毒犬正在搜索毒品!")
class PoliceDog(Dog):
def work(self):
print("军犬正在追击敌人!")
class BlindDog(Dog):
def work(self):
print("导盲犬正在领路!")
"""
目前看来,虽然子类可以不继承自父类,但是只要它们在各自内部都实现一个同名的方法work(),然后实例化传递给调用者貌似也可以执行成功;
但是,如果现在也来了一个动物,例如猫,它也具有work()方法。那么,当它实例化后传给调用者时,并不会报错,而是成功执行;
显然,这不是我们想要的结果,所以在实际开发过程中对于多态的使用,还是建议使用继承来确保结果的正确性和一致性;
"""
class Cat(object):
def work(self):
print("牧羊犬在执勤!")
cat = Cat() # 当我们把猫类Cat实例化后传给调用者,并不会报错,但结果不是我们想要的;
# 定义人类;
class Person(object):
# 具有姓名和年龄属性;
def __init__(self, name, dog=None):
self.name = name
self.dog = dog
def work_with_dog(self):
# 如果调用者没有传入狗,或它传入的动物不属于狗类(Dog),则不执行下面这段代码;
if self.dog and isinstance(self.dog, Dog):
self.dog.work() # 只需要调用所有子类的父类(Dog)中的公共方法,即可保证子类实现不同的效果;
3. 将上述各类进行实例化,并将子类对象传递给调用者,可以看到它们实现效果不同;
# 实例化一个人类,且分别实例化狗类;
someone = Person("唐三")
drug_dog = DrugDog()
police_dog = PoliceDog()
blind_dog = BlindDog()
someone.dog = drug_dog
someone.work_with_dog()
# 程序执行结果: 缉毒犬正在搜索毒品!
someone.dog = police_dog
someone.work_with_dog()
# 程序执行结果: 军犬正在追击敌人!
someone.dog = blind_dog
someone.work_with_dog()
# 程序执行结果: 导盲犬正在领路!
四、实践出真知
虽然Python面向对象的语法比较简单,但是它的知识点也是比较繁琐,如果想要准确、高效并且灵活地运用它们解决实际问题,需要不断地在实践中去练习和检验,这样才能真正掌握面向对象编程的思想和精髓。 因此,这个部分我们解题的重心就不是对代码进行逐一解释,而是更多的关注解决问题的流程和思路。
4.1 小试牛刀
实际需求:
- 定义学生类,学生有姓名、学号、年纪、python分数、java分数以及c语言分数
还有学习和打游戏行为; - 创建5个学生对象,添加到学生列表中;
- 对学生列表中的学生对象按照python和c语言的平均成绩做降序排序;
# 1. 定义一个学生类,它包含姓名、学号、年纪、Python分数、java分数以及C语言分数;
class Student(object):
def __init__(self, name, stu_id, age, python_score, c_score):
self.name = name
self.stu_id = stu_id
self.age = age
self.python_score = python_score
self.c_score = c_score
self.avg_score = (self.python_score+self.c_score) / 2
def study(self):
print("%s正在学习,请勿打扰!" % self.name)
def play_game(self):
print(f"{self.name}正在打游戏,他今年{self.age}岁了!")
def __str__(self): # 打印对象信息;
string = ""
string += "我是%s, 我的Python成绩为%d, C语言成绩为:%d" % (self.name, self.python_score, self.c_score)
string += ",平均分为: %d" % self.avg_score
return string
if __name__ == '__main__':
# 2.实例化5个对象,并将它们存储在列表中;
xiao_san = Student("小三", "123456", 25, 100, 100)
xiao_wu = Student("小舞", "456789", 26, 98, 97)
rong = Student("宁荣荣", "134679", 25, 99, 98)
mu_bai = Student("戴沐白", "159753", 28, 93, 92)
xiao_ao = Student("奥斯卡", "147896", 27, 95, 98)
students = [xiao_san, xiao_wu, rong, mu_bai, xiao_ao]
# 3. 对【列表】中的【学生对象】按照【python和c语言平均成绩】做【降序排序】;
students.sort(key=lambda x: (x.python_score + x.c_score) >> 1, reverse=True) # sorted()
for ele in students:
print(ele)
程序执行结果:
4.2 文化差异
实际需求:
- 定义Person类:属性为name和age;方法为eat ,功能是【吃饭】。
- 定义ZhHuman类继承Person类:属性为name 、age 和color(肤色);方法为eat,功能是【使用筷子吃饭】。
- 定义USHuman类继承Person类:属性为name 、age和color(肤色);方法为eat,功能为【使用刀叉吃饭】。
- 定义AfricaHuman类继承Person类:属性为name、age和color(肤色);方法为eat,功能为【用手吃饭】。
- 定义Cat类:属性为name、age和variety(品种);方法为eat,内容为【猫吃鱼】。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print("正在吃饭!")
class ZhHuman(Person):
def __init__(self, name, age, color):
super().__init__(name, age)
self.color = color
def eat(self):
print("中国人使用筷子吃饭!")
class USHuman(Person):
def __init__(self, name, age, color):
super(USHuman, self).__init__(name, age)
self.color = color
def eat(self):
print("美国人使用刀叉吃饭!")
class AfricanHuman(Person):
def __init__(self, name, age, color):
Person.__init__(self, name, age)
self.color = color
def eat(self):
print("非洲人用手吃饭!")
class Cat(object):
def __init__(self, name, age, variety):
self.name = name
self.age = age
self.variety = variety
@staticmethod
def eat():
print("猫吃鱼!")
def start_eat(human):
human.eat()
p1 = Person('张三',30)
p2 = ZhPerson('李四',30,'黄种人')
p3 = USPerson('王五',30,' 白种人')
p4 = AfricaPerson('李四',30,'黑色')
cat = Cat('李四',30,'波斯猫')
start_eat(cat)
start_eat(p1)
start_eat(p2)
start_eat(p3)
start_eat(p4)
程序执行结果:
4.3 温馨小家
①需求及分析: 将小于房间面积的家具摆放到房间中,这里的房间面积指的是房间的剩余可用面积;
- 家具类: 每个家具拥有自己的名称和占地面积;
- 房间类: 具有房屋类型、总面积和家具列表属性;具有添加和展示家具的方法;
②代码实现:
# 家具类;
class Furniture(object):
def __init__(self, name, area):
self.name = name
self.area = area
# 房间类;
class House(object):
def __init__(self, type_, total_area):
self.type_ = type_
self.total_area = total_area
self.remain_area = total_area * 0.7
self.furniture_list = []
# 添加家具;
def add_furniture(self, furniture: Furniture): # 对象也可以作为方法参数进行传递;
# 每次添加家具之前先判断房间剩余面积是否大于该家具的面积;
if self.remain_area < furniture.area:
print(f"{furniture.name}的面积为{furniture.area}, 房间剩余面积为{self.remain_area},太大了,装不下!")
return
self.furniture_list.append(furniture.name)
# 添加以后,房间剩余面积应该进行更新;
self.remain_area -= furniture.area
# 展示家具;
def __str__(self):
return "户型为:{}, 总面积为:{}平米, 剩余面积为:{}平米, 家具列表为:{}".format(self.type_, self.total_area,
self.remain_area, self.furniture_list)
# 主程序入口;
if __name__ == '__main__':
# 先实例化一个房间对象;
my_house = House("一室一厅", 144)
print(my_house) # 户型为:一室一厅, 总面积为:144平米, 剩余面积为:100.8平米, 家具列表为:[]
# 实例化多个家具对象;
bed = Furniture("席梦思床", area=32)
chest = Furniture("衣柜", area=28)
table = Furniture("桌子", area=25)
sofa = Furniture("沙发", area=21)
# 将创建好的家具放到房间中;
my_house.add_furniture(bed)
my_house.add_furniture(chest)
my_house.add_furniture(table)
my_house.add_furniture(sofa) # 沙发的面积为21, 房间剩余面积为15.799999999999997,太大了,装不下!
# 打印房间信息;
print(my_house) # 户型为:一室一厅, 总面积为:144平米, 剩余面积为:15.799999999999997平米, 家具列表为:['席梦思床', '衣柜', '桌子']
4.4 班级学生信息管理
①需求及分析:
- 学生类: 它具有学号、姓名、年龄、性别和成绩属性;
- 班级类: 具有班级名称和班级中的学生信息属性(使用列表存储学生对象),对应的方法如下
- 具有增加学生信息的功能(学生名不能重复);
- 查看班级中所有学生的信息;
- 查看指定学号学生的信息;
- 查看班级中成绩不及格(小于60分)学生的信息;
- 将班级中的学生按照成绩降序排序;
②代码实现:
# 先定义一个学生类;
class Student(object):
def __init__(self, stu_id, stu_name, stu_age, stu_gender, stu_score):
self.stu_id = stu_id
self.stu_name = stu_name
self.stu_age = stu_age
self.stu_gender = stu_gender
self.stu_score = stu_score
def __str__(self):
return f"学号为:{self.stu_id}, 姓名为:{self.stu_name}, 年龄为:{self.stu_age}, 性别为:{self.stu_gender}, 成绩为{self.stu_score}!"
# 再定义一个班级类;
class Class(object):
def __init__(self, name):
self.name = name
self.stu_infos = []
# 添加学生对象至列表中;
def add_stu(self, student: Student):
# 我们不允许学生名重复,所以每次添加学生对象前都需要遍历学生列表进行一一判断;
for stu in self.stu_infos:
if stu.stu_name == student.stu_name:
print("该学生已存在,请重新输入!")
break
else: # for...in循环正常结束后执行else语句:如果新添加的学生不存在列表中,即可添加;
self.stu_infos.append(student)
# 查看班级中所有学生信息;
def query_all(self):
# 非空检测,如果班级学生列表为空,提示用户添加学生信息!
if not self.stu_infos:
print("该班级还没有学生,请先添加学生信息!")
return
print(f"【{self.name}全部学生信息如下】".center(46, "-"))
for stu in self.stu_infos:
print(stu)
# 查看指定学号学生的信息;
def query_stu(self, stu_id):
stu_len = len(self.stu_infos)
i = 0
while i < stu_len:
if self.stu_infos[i].stu_id == stu_id:
print("您查询的学号为{}的学生信息为:".format(stu_id), self.stu_infos[i])
break
i += 1
else: # while...else的用法与for...else一致,查询无果后返回的信息;
print("您查询的学生信息不存在, 请重新输入!")
# 查看班级中学生成绩不及格的学生信息;
def query_score(self):
# 这里我们使用Python内置函数filter()直接过滤掉成绩不合格的学生信息,并对其进行展示(也可以遍历筛选);
result = filter(lambda x: x.stu_score < 60, self.stu_infos) # filter()函数返回的是一个可迭代对象;
print("【成绩不及格学生信息如下】".center(48, "-"))
for ele in result:
print(ele)
# 将班级中的学生按照成绩进行降序排序;
def sort(self):
# 这里既可以使用list.sort(),也可以使用sorted(),它们区别是前者为in-place即在源列表上直接进行排序,后者排序后返回一个新的列表;
# 当然了,我们也可以使用快速排序、归并排序、冒泡排序、堆排序或其它排序算法进行手动降序;
self.stu_infos.sort(key=lambda x: x.stu_score, reverse=True) # reverse=Fasle默认为升序;
self.query_all() # 将排序后的学生信息返回给用户;
# 主程序入口(可执行性已经检验,由于结果较长这里不进行展示)
if __name__ == '__main__':
# 实例化一个班级对象以及多个学生对象;
cls_1 = Class("一班")
xiao_san = Student(2, "小三", 25, "男", 85)
xiao_wu = Student(56, "小舞", 21, "女", 95)
ao_si_ka = Student(18, "奥斯卡", 23, "男", 86)
rong_rong = Student(24, "荣荣", 24, "男", 98)
pang_zi = Student(19, "胖子", 22, "男", 58)
zhu_qing = Student(25, "竹青", 23, "女", 100)
mu_bai = Student(10, "沐白", 25, "男", 97)
# 添加各个学生对象至指定班级中;
cls_1.add_stu(xiao_san)
cls_1.add_stu(xiao_wu)
cls_1.add_stu(ao_si_ka)
cls_1.add_stu(rong_rong)
cls_1.add_stu(pang_zi)
cls_1.add_stu(zhu_qing)
cls_1.add_stu(mu_bai)
# 查询班级中全部学生信息;
cls_1.query_all()
# 查询指定学号学生的信息;
cls_1.query_stu(2)
cls_1.query_stu(58) # 因为不存在会报错;
# 查询不及格学生信息;
cls_1.query_score()
# 对学生信息按照成绩进行降序排序;
cls_1.sort()