面向对象编程的三大特征:封装、继承和多态。
注意:在python面向对象编程中,子类对象可以传递给父类类型
一、封装
在Python中,封装是面向对象编程中的一种重要概念,它可以帮助我们实现数据隐藏、信息保护和代码复用。在Python中,通过使用访问控制修饰符(属性和方法前加上两个下划线)来实现封装。
封装就是:我写了一个类,我将一个类的属性、方法全部包装到了一个类中。我对类中某些方法和属性进行了隐藏,(外部实例化之后,无法通过对象.方法或对象.属性来进行直接调用)。因为我不想让外部了解我的实现机理或属性,但是会留出一些公有的方法来供外部间接调用这些“封装”好的属性和方法!这就是封装!!!
下面是Python中常用的封装方式:
公有成员:默认情况下,Python中的所有成员(属性和方法)都是公有的,可以被外部访问。例如:
class MyClass:
def __init__(self):
self.public_var = 10
def public_method(self):
return "This is a public method."
obj = MyClass()
print(obj.public_var) # 10
print(obj.public_method()) # This is a public method.
私有成员:在成员名字前面加上两个下划线 __
,可以将其定义为私有成员,外部无法直接访问。(这种前面加两个下划线的私有成员被称为伪私有成员)例如:
class MyClass:
def __init__(self):
self.__private_var = 20
def __private_method(self):
return "This is a private method."
obj = MyClass()
# 尝试访问私有属性和方法
print(obj.__private_var) # 会报错
print(obj.__private_method()) # 会报错
上述代码会报错,原因是在类外部试图访问私有成员(私有属性、私有方法) 。
访问类中的私有成员
那么我们该如何在类外访问类中的私有成员呢?答案是:我们可以通过类中的公有方法来访问类中的私有成员。
class MyClass:
def __init__(self):
self.__private_var = 20
def __private_method(self):
return "This is a private method."
# 访问私有属性 __private_var
def public_method(self):
return self.__private_var
# 访问私有方法 __private_method
def get_private_method(self):
return self.__private_method()
obj = MyClass()
# 尝试访问私有属性和方法
print(obj.__private_var) # 会报错
print(obj.__private_method()) # 会报错
print(obj.public_method()) # 20
print(obj.get_private_method()) # This is a private method.
根据上面的代码,我们可以看到, 当直接在类外访问私有属性或私有方法时,程序会报错。我们在类中写了两个公有方法,分别访问类中的私有属性和私有方法,运行程序,访问成功。
通过封装,我们可以隐藏类的内部实现细节,提高代码的安全性和可维护性。同时,封装也有助于减少代码的耦合度,使代码更易于理解和调试。
细节:由于python语言的动态特性,会出现伪私有属性的情况
其他博客看到的:
在Python中,可以通过在属性名前加上__
实现伪私有属性,但这并不是真正的私有,只是一种变形,实际上,Python并没有真正意义上的私有属性。
伪私有属性会被重命名为_类名__属性名
。这种重命名是由Python内部的机制在编译时自动完成的。
这种做法的目的是防止子类重写该属性。
例如,定义一个类并使用伪私有属性:
在这个示例中,__pseudo_private_var
是一个伪私有属性,虽然我们在类的外部尝试直接访问它会报错,但实际上它的名称已经被改写成了 _MyClass__pseudo_private_var
,我们可以通过这个改写后的名称来访问伪私有属性。另外,我们也可以通过定义公有方法来间接地访问这个伪私有属性。
封装练习
●定义Account类
1)Account类要求具有属性:姓名(长度为2-4位)、余额(必须>20)、密码(必须是六位),如果不满足,则给出提示信息,并给默认值(程序员自己定)
2)通过set_xxx的方法给Account的属性赋值。
3)编写方法query_info()接收姓名和密码,如果姓名和密码正确,返回该账号信息
# 分析如下:
# 类名:Account
# 私有属性:姓名(长度为2-4位)、余额(必须>20)、密码(必须为6位)
# 构造器: 无,默认是一个无参的构造器
# 方法:set_xxx(self, 属性名) 进行赋值,并且对各个接收到的数据进行校验
# 方法query_info(self, name, pwd) 而且需要验证,才返回响应信息
class Account:
__name = None
__balance = None
__pwd = None
def set_name(self, name):
# 2-4位
if 2<= len(name) <=4:
self.__name = name
else:
print("名字的长度不在2-4位之间!")
def set_balance(self, balance):
# 余额(必须>20)
if balance > 20:
self.__balance = balance
else:
print("余额必须大于20!")
def set_pwd(self, pwd):
# 密码必须是6位
if len(pwd) == 6:
self.__pwd = pwd
else:
print("密码必须设置为6位!")
def query_info(self, name, pwd):
if name == self.__name and pwd == self.__pwd:
return f"账户信息如下: \n账户名: {self.__name}, 账户余额: {self.__balance}"
else:
return "请输入正确的账户名和密码"
account = Account()
account.set_name("tim")
account.set_pwd("000000")
account.set_balance(10000)
print(account.query_info("tim", "000000"))
输出结果:
账户信息如下:
账户名: tim, 账户余额: 10000
部分参考自:
https://www.cnblogs.com/blackmatrix/p/5600830.html
二、继承
2.1 什么是继承?
在Python中,继承是面向对象编程中的一个重要概念,它允许一个类(称为子类)继承另一个类(称为父类)的属性和方法。通过继承,子类可以复用父类的代码,并且可以在不改变原有代码的情况下添加新的功能或修改已有功能。字面意思就是,一个类是另一个类的子类,那么这个类就可以拥有和父类一样的属性、方法。这就好比是现实当中,儿子会遗传父亲的性格、长相等。
2.2 为什么需要继承?
我们编写了两个类,一个是Pupil类(小学生),一个是Graduate(大学毕业生)。问题:两个类的属性和方法有很多是相同的,怎么办?这就需要我们的继承特性。
我们先来看看没有继承的代码,如下所示。
class Pupil:
name = None
age = None
__score = None
def __init__(self, name, age):
self.name = name
self.age = age
def show_info(self):
print(f"name={self.name} age={self.age} score={self.__score}")
def set_score(self, score):
self.__score = score
def testing(self):
print(f"小学生在考小学数学")
class Graduate:
name = None
age = None
__score = None
def __init__(self, name, age):
self.name = name
self.age = age
def show_info(self):
print(f"name={self.name} age={self.age} score={self.__score}")
def set_score(self, score):
self.__score = score
def testing(self):
print(f"大学生在考高等数学")
student1 = Pupil("小白", 10)
student1.testing()
student1.set_score(70)
student1.show_info()
print("-----------------")
student2 = Graduate("小黑", 20)
student2.testing()
student2.set_score(80)
student2.show_info()
输出结果:
小学生在考小学数学
name=小白 age=10 score=70
-----------------
大学生在考高等数学
name=小黑 age=20 score=80
分析上述代码我们可知,我们创建了两个类,Pupil类和Graduate类,Pubil类中有name、age、_score属性,有显示信息的方法show_info(),有设置分数的方法set_score(),有测试的方法testing();Graduate类中也有name、age、_score属性,有显示信息的方法show_info(),有设置分数的方法set_score(),有测试的方法testing()。
我们可以看到,除了testing()方法执行的功能不一样外,属性和其余的方法执行的功能均一样。那么我们可以创建一个新类Student,将这两个类共有的属性和方法存放在新类中,随后使得这两个类与新类构成继承关系。修改后的代码如下。
# 父类
class Student:
name = None
age = None
__score = None
def __init__(self, name, age):
self.name = name
self.age = age
def show_info(self):
print(f"name={self.name} age={self.age} score={self.__score}")
def set_score(self, score):
self.__score = score
class Pupil(Student):
def testing(self):
print(f"小学生在考小学数学")
class Graduate(Student):
def testing(self):
print(f"大学生在考高等数学")
student1 = Pupil("小白", 10)
student1.testing()
student1.set_score(70)
student1.show_info()
print("-----------------")
student2 = Graduate("小黑", 20)
student2.testing()
student2.set_score(80)
student2.show_info()
输出结果:
小学生在考小学数学
name=小白 age=10 score=70
-----------------
大学生在考高等数学
name=小黑 age=20 score=80
2.3 注意细节
1、子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
class Base:
# 公共属性
n1 = "public attribute"
# 私有属性
__n2 = "private attribute"
# 公共方法
def public_method(self):
return "public_method"
# 私有方法
def __private_method(self):
return "private_method"
class DerivedBase(Base):
# 访问公有成员
def visit_public(self):
print(self.n1)
print(self.public_method())
def visit_private(self):
print(self.__n2)
print(self.__private_method)
A = DerivedBase()
A.visit_public() # public attribute
# public_method
A.visit_private() # 报错
在子类中通过访问父类的公有方法进一步访问父类的私有成员。 代码如下所示。
class Base:
# 公共属性
n1 = "public attribute"
# 私有属性
__n2 = "private attribute"
# 公共方法
def public_method(self):
return "public_method"
# 私有方法
def __private_method(self):
return "private_method"
# 提供公共方法,返回私有成员
def method(self):
print("属性", self.n1, self.__n2)
print(self.__private_method())
class DerivedBase(Base):
# 通过父类的公有方法,访问父类的私有成员
def visit(self):
self.method()
A = DerivedBase()
A.visit()
输出结果:
属性 public attribute private attribute
private_method
2、python编程语言中,object类是所有类的一个基类
我们将光标停在如下红框位置,随后按下ctrl+H,得到右栏内容,可以清晰地看到,object类是所有类的一个基类。
3、python支持多重继承
语法格式:
class DerivedClassName(Base1,Base2,Base3....):
.....
示例代码
class A:
n1 = 100
def sing(self):
print("A sing()...", self.n1)
class B:
n2 = 200
def dance(self):
print("B dance()...", self.n2)
class C(A, B):
def visit_AB(self):
self.sing()
self.dance()
return self.n1, self.n2
c = C()
print(c.visit_AB())
输出结果:
A sing()... 100
B dance()... 200
(100, 200)
4、在多重继承中,如果有同名的成员,遵守从左到右的继承优先级(即:写左边的父类优先级高,写在右边的父类优先级低)
class A:
n1 = 100
class B:
n1 = 20
class C(A, B):
pass
c = C()
print(c.n1) # 100
class A:
n1 = 100
class B:
n1 = 20
class C(B, A):
pass
c = C()
print(c.n1) # 20
继承练习题目
编写Computer类,包含CPU、内存、硬盘等属性
1) get_details方法用于返回Computer的详细信息
2)编写PC子类,继承Computer类,添加特有属性【品牌brand】
3)编写NotePad子类,继承Computer类,添加特有属性【color】
4)完成测试,创建PC和NotePad对象,分别给对象中特有的属性赋值,以及从Computer类继承的属性赋值,并使用方法打印输出信息
思路分析
1.父类:Computer
2.公共属性:CPU(cpu)、内存(memory)、硬盘(disk)
3.构造器:__init__(self, cpu , memory,disk)
4.方法: get_details(self)
5.子类:PC(Computer)
6.公共属性:brand
7.构造器: __init__(self,cpu , memory,disk , brand),有一个新的知识点
8.方法: print_info(self) 完成功能:输出pc对象的信息(即属性的信息)
class Computer:
cpu = None
memory = None
disk = None
def __init__(self, cpu, memory, disk):
self.cpu = cpu
self.memory = memory
self.disk = disk
def get_details(self):
return f"cpu: {self.cpu}\t内存: {self.memory}\t硬盘: {self.disk}"
class PC(Computer):
brand = None
def __init__(self, cpu, memory, disk, brand):
# 通过super().xx 方式可以去调用父类的方法
# 这里,我们通过super().__init__(cpu, memory, disk)去调用父类的构造器,完成对父类属性的初始化任务
# self.brand = brand 表示子类特有属性,由子类的构造器完成初始化任务
self.brand = brand
super().__init__(cpu, memory, disk)
def print_info(self):
# 完成打印当前对象的信息
print(f"品牌:{self.brand}\t{self.get_details()}")
pc = PC("intel", 32, 1000, "戴尔")
pc.print_info()
输出结果:
品牌:戴尔 cpu: intel 内存: 32 硬盘: 1000
5. 调用父类成员细节问题
在Python中,super()
函数是一个内置函数,通常用于调用父类的方法。通过 super()
方法,子类可以直接调用父类中的方法,而不需要显式指定父类的名称。super()
函数通常用在子类中的构造函数中,以便在子类中扩展父类的功能。
如果子类和父类出现同名成员,可以通过父类名或super()访问父类的成员
基本语法
1)访问父类成员方式1
-访问成员变量:父类名,成员变量
-访问成员方法:父类名,成员方法(self)
2)访问父类成员方式2
-访问成员变量: super().成员变量
-访问成员方法:super().成员方法()
代码如下:
class A:
n1 = 100
def run(self):
print("A-run()...")
class B(A):
n1 = 200
def say(self):
print(f"父类的n1:{A.n1} 本类的n1:{self.n1}") # 父类的n1:100 本类的n1:200
# 调用父类的run
A.run(self) # A-run()...
# 调用本类的run
self.run() # B-run()...
def hi(self):
print(f"父类的n1 {super().n1}") # 父类的n1 100
# 调用父类的run
super().run() # A-run()...
def run(self):
print(f"B-run()...")
b = B()
b.say()
b.hi()
6、访问不限于直接父类,而是建立从子类向上级父类的查找关系
重写(override)
重写又称覆盖(override),指子类定义了与父类同名的属性和方法,从而覆盖了父类中的属性和方法。当子类对象调用这个属性和方法时,将执行子类中的属性和方法,而不是父类中的属性和方法。
class A:
n1 = 100
def run(self):
print(f"A-run...")
class B(A):
# 重写A类中的n1属性
n1 = 10
# 重写A类中的run方法
def run(self):
print(f"B-run...")
b = B()
print(b.n1) # 10
b.run() # B-run...
方法的覆盖允许子类在不改变方法名称的情况下重写父类的方法实现,从而实现特定于子类的行为。在实际开发中,方法的覆盖是面向对象编程中常用的技术,可以根据需要在子类中定制特定的行为。
需要注意的是,当子类覆盖父类的方法时,建议遵循相同的方法签名(参数列表和返回类型),以确保代码的一致性和易维护性。
例题练习
●编程题
1)编写一个Person类,包括属性(name、age),构造方法、say方法(返回Person自我介绍的字符串)
2)编写一个Student类,继承Person类,增加属性(id、score),以及构造方法,重写say方法(返回Student自我介绍的信息)
3)分别创建Person和Student对象调用say方法输出自我介绍,体会里与的作用
class Person:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
return f"name:{self.name}\tage:{self.age}"
class Student(Person):
id = None
score = None
def __init__(self, id, score, name, age):
# 子类的特有的属性,我们自己完成初始化
self.id = id
self.score = score
# 调用父类的构造器完成继承父类的属性的初始化
super().__init__(name, age)
def say(self):
return f"id:{self.id}\tscore:{self.score}\t{super().say()}"
person = Person("小白", 20)
student = Student(1, 100, "小黑", 30)
print(person.say()) # name:小白 age:20
print(student.say()) # id:1 score:100 name:小黑 age:30
三、多态
3.1 什么是多态?
举一个现实中的例子,同样的一件事情,不同的人处理起来,他们的实现过程是完全不同的,会表现出不同的形态。比如:都是吃饭这个事情,中国人表现的是用筷子吃饭,而美国人表现的是用叉刀吃饭。这个就是相同的事情,表现出了不同的“形态”。
(1)多态通常作用在继承的关系上。
(2)多态指的是方法的多态,多态与属性无关。
(3)多态顾名思义是多种状态,不同的对象调用相同的方法,表现出不同的状态,称为多态
先看一个问题
请编写一个程序,Master类中有一个feed(喂食)方法,可以完成主人给动物喂食物的信息
使用传统的方法(不使用多态)来解决上面这个问题,如下所示。
# 不涉及多态
class Food:
name = None
def __init__(self, name):
self.name = name
class Fish(Food):
# 特有的属性和方法
pass
class Bone(Food):
# 特有的属性和方法
pass
class Animal:
name = None
def __init__(self, name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
class Master:
name = None
def __init__(self, name):
self.name = name
# 给猫猫喂鱼
def feed_cat(self, cat: Cat, fish: Fish):
print(f"主人{self.name}给动物{cat.name}喂的食物是{fish.name}")
# 给小狗喂骨头
def feed_dog(self, dog: Dog, bone: Bone):
print(f"主人{self.name}给动物{dog.name}喂的食物是{bone.name}")
master = Master("小白")
cat = Cat("小花猫")
fish = Fish("黄花鱼")
dog = Dog("大黄狗")
bone = Bone("大棒骨")
master.feed_cat(cat, fish)# 主人小白给动物小花猫喂的食物是黄花鱼
master.feed_dog(dog, bone)# 主人小白给动物大黄狗喂的食物是大棒骨
传统方法带来的问题是什么?如何解决?
问题是:代码的复用性不高,而且不利于代码维护和功能拓展
解决方案:使用多态
使用多态
class Animal:
def cry(self):
pass
class Cat(Animal):
def cry(self):
print("小猫 喵喵叫...")
class Dog(Animal):
def cry(self):
print("小狗 汪汪叫...")
class Pig(Animal):
def cry(self):
print("小猪 哼哼叫...")
# 在python面向对象编程中,子类对象可以传递给父类类型
def func(animal: Animal):
print(f"animal 类型是{type(animal)}")
animal.cry()
dog = Dog()
pig = Pig()
cat = Cat()
func(dog) # animal 类型是<class '__main__.Dog'>
# 小狗 汪汪叫...
func(pig) # animal 类型是<class '__main__.Pig'>
# 小猪 哼哼叫...
func(cat) # animal 类型是<class '__main__.Cat'>
# 小猫 喵喵叫...
●多态的好处
1)增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal),上述代码说明
2)增加了程序的可扩展性,通过继承Animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
使用多态优化主人喂动物问题
# 不涉及多态
class Food:
name = None
def __init__(self, name):
self.name = name
class Fish(Food):
# 特有的属性和方法
pass
class Bone(Food):
# 特有的属性和方法
pass
class Animal:
name = None
def __init__(self, name):
self.name = name
class Dog(Animal):
pass
class Cat(Animal):
pass
class Master:
name = None
def __init__(self, name):
self.name = name
# 给动物喂食物
def feed(self, animal: Animal, food: Food):
print(f"主人{self.name}给动物{animal.name}喂的食物是{food.name}")
master = Master("小白")
cat = Cat("小花猫")
fish = Fish("黄花鱼")
dog = Dog("大黄狗")
bone = Bone("大棒骨")
master.feed(cat, fish)# 主人小白给动物小花猫喂的食物是黄花鱼
master.feed(dog, bone)# 主人小白给动物大黄狗喂的食物是大棒骨