目录
一、面向对象是什么?
二、面向对象编程主的基本概念
三、类
四、对象
4.1 创建对象的格式为
4.2 添加和获取对象的属性
4.3 在方法内通过self获取对象属性
4.4 __init__方法
说明:
4.5 有参数的__init__方法
说明:
注意:
4.6 __str__方法
说明:
4.7 类和对象的关系
五、 继承
5.1 单继承:子类只继承一个父类
5.2 多继承:子类继承多个父类
5.3 子类重写父类的同名属性和方法
5.4 子类调用父类同名属性和方法
5.5 多层继承
5.6 通过super()来调用父类中的方法
六、封装
6.1 封装的意义
6.2 私有权限:在属性名和方法名 前面 加上两个下划线 __
6.3 修改私有属性的值
七、多态
7.1 什么是多态?
7.2 如何在程序中使用多态
7.3 多态的好处
一、面向对象是什么?
面向对象编程(Object-oriented programming,简称OOP)是一种编程范式,它的核心思想是通过将数据和操作数据的方法(函数)封装在一起,形成对象,然后通过对象之间的交互来实现程序的功能
二、面向对象编程主的基本概念
- 类(Class):类是对象的抽象,定义了对象的属性(成员变量)和行为(方法)。可以将类看作是对象的模板或蓝图。
- 对象(Object):对象是类的实例,具体化了类的属性和行为。每个对象都有自己的状态(属性值)和行为(方法)。
- 封装(Encapsulation):封装是指将数据和操作数据的方法捆绑在一起形成类,并对外部隐藏对象的内部细节。通过封装,可以实现信息的隐藏和保护。
- 继承(Inheritance):继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,并可以在此基础上进行扩展或修改。通过继承,可以实现代码的重用和层次化组织。
- 多态(Polymorphism):多态是指同一个方法名可以在不同的类中具有不同的实现方式,使得程序可以根据对象的实际类型来调用相应的方法。多态性提高了代码的灵活性和可扩展性。
三、类
面向对象的语言当中,“类”就是用来模拟现实事物的。
那么模拟现实世界的事物通常从两方面模拟:
- 属性:事物的特征描述信息,用于描述某个特征“是什么”。 静
- 行为:事物的能力行动方案,用于说明事物“能做什么”。
类中也有属性、行为两个组成部分,而“对象”是类的具体实例。例如:
- 类:抽象的,是一张“手机设计图”。
- 对象:具体的,是一个“真正的手机实例”。
类可以描述世间万物,类都需要有类名,也应该具备一些属性和行为
- 类的关键字:
class
- 类的名称:类名
- 类的属性:一组数据
- 类的方法:允许进行操作的方法(行为)
定义类:
class 类名:方法列表
定义一个Dog类:
# class Hero: # 经典类(旧式类)定义形式
# class Hero():class Hero(object): # 新式类定义形式def info(self):print("英雄各有见,何必问出处。")
说明:
-
定义类时有2种形式:新式类和经典类,上面代码中的Hero为新式类,前两行注释部分则为经典类;
-
object 是Python 里所有类的最顶级父类;
-
类名 的命名规则按照"大驼峰命名法";
- info 是一个实例方法,第一个参数一般是self,表示实例对象本身,当然了可以将self换为其它的名字,其作用是一个变量 这个变量指向了实例对象
四、对象
某一个具体事物的存在 ,在现实世界中可以是看得见摸得着的。可以是直接使用的
python中,可以根据已经定义的类去创建出一个或多个对象。
4.1 创建对象的格式为
对象名1 = 类名()
对象名2 = 类名()
对象名3 = 类名()
示例代码:
class Hero(object): # 新式类定义形式"""info 是一个实例方法,类对象可以调用实例方法,实例方法的第一个参数一定是self"""def info(self):"""当对象调用实例方法时,Python会自动将对象本身的引用做为参数,传递到实例方法的第一个参数self里"""print(self) print("self各不同,对象是出处。")# Hero这个类 实例化了一个对象 taidamier(泰达米尔)
taidamier = Hero()# 对象调用实例方法info(),执行info()里的代码
# . 表示选择属性或者方法
taidamier.info()print(taidamier) # 打印对象,则默认打印对象在内存的地址,结果等同于info里的print(self)
print(id(taidamier)) # id(taidamier) 则是内存地址的十进制形式表示
4.2 添加和获取对象的属性
class Hero(object):"""定义了一个英雄类,可以移动和攻击"""def move(self):"""实例方法"""print("正在前往事发地点...")def attack(self):"""实例方法"""print("发出了一招强力的普通攻击...")# 实例化了一个英雄对象 泰达米尔
taidamier = Hero()# 给对象添加属性,以及对应的属性值
taidamier.name = "泰达米尔" # 姓名
taidamier.hp = 2600 # 生命值
taidamier.atk = 450 # 攻击力
taidamier.armor = 200 # 护甲值# 通过.成员选择运算符,获取对象的属性值
print("英雄 %s 的生命值 :%d" % (taidamier.name, taidamier.hp))
print("英雄 %s 的攻击力 :%d" % (taidamier.name, taidamier.atk))
print("英雄 %s 的护甲值 :%d" % (taidamier.name, taidamier.armor))# 通过.成员选择运算符,获取对象的实例方法
taidamier.move()
taidamier.attack()
4.3 在方法内通过self获取对象属性
class Hero(object):"""定义了一个英雄类,可以移动和攻击"""def move(self):"""实例方法"""print("正在前往事发地点...")def attack(self):"""实例方法"""print("发出了一招强力的普通攻击...")def info(self):"""在类的实例方法中,通过self获取该对象的属性"""print("英雄 %s 的生命值 :%d" % (self.name, self.hp))print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))# 实例化了一个英雄对象 泰达米尔
taidamier = Hero()# 给对象添加属性,以及对应的属性值
taidamier.name = "泰达米尔" # 姓名
taidamier.hp = 2600 # 生命值
taidamier.atk = 450 # 攻击力
taidamier.armor = 200 # 护甲值# 通过.成员选择运算符,获取对象的实例方法
taidamier.info() # 只需要调用实例方法info(),即可获取英雄的属性
taidamier.move()
taidamier.attack()
4.4 __init__方法
class Hero(object):"""定义了一个英雄类,可以移动和攻击"""# Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,__init__()就是一个魔法方法,通常用来做属性初始化 或 赋值 操作。# 如果类面没有写__init__方法,Python会自动创建,但是不执行任何操作,# 如果为了能够在完成自己想要的功能,可以自己定义__init__方法,# 所以一个类里无论自己是否编写__init__方法 一定有__init__方法。def __init__(self):""" 方法,用来做变量初始化 或 赋值 操作,在类实例化对象的时候,会被自动调用"""self.name = "泰达米尔" # 姓名self.hp = 2600 # 生命值self.atk = 450 # 攻击力self.armor = 200 # 护甲值def move(self):"""实例方法"""print("正在前往事发地点...")def attack(self):"""实例方法"""print("发出了一招强力的普通攻击...")# 实例化了一个英雄对象,并自动调用__init__()方法
taidamier = Hero()# 通过.成员选择运算符,获取对象的实例方法
taidamier.info() # 只需要调用实例方法info(),即可获取英雄的属性
taidamier.move()
taidamier.attack()
说明:
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用__init__(self)
中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。
4.5 有参数的__init__方法
class Hero(object):"""定义了一个英雄类,可以移动和攻击"""def __init__(self, name, skill, hp, atk, armor):""" __init__() 方法,用来做变量初始化 或 赋值 操作"""# 英雄名self.name = name# 技能self.skill = skill# 生命值:self.hp = hp# 攻击力self.atk = atk# 护甲值self.armor = armordef move(self):"""实例方法"""print("%s 正在前往事发地点..." % self.name)def attack(self):"""实例方法"""print("发出了一招强力的%s..." % self.skill)def info(self):print("英雄 %s 的生命值 :%d" % (self.name, self.hp))print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))# 实例化英雄对象时,参数会传递到对象的__init__()方法里
taidamier = Hero("泰达米尔", "旋风斩", 2600, 450, 200)
gailun = Hero("盖伦", "大宝剑", 4200, 260, 400)# print(gailun)
# print(taidamier)# 不同对象的属性值的单独保存
print(id(taidamier.name))
print(id(gailun.name))# 同一个类的不同对象,实例方法共享
print(id(taidamier.move()))
print(id(gailun.move()))
说明:
-
通过一个类,可以创建多个对象,就好比 通过一个模具创建多个实体一样
-
__init__(self)
中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么__init__(self)
中出了self作为第一个形参外还需要2个形参,例如__init__(self,x,y)
注意:
- 在类内部获取 属性 和 实例方法,通过self获取;
-
在类外部获取 属性 和 实例方法,通过对象名获取。
-
如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址;
- 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法。
4.6 __str__方法
class Hero(object):"""定义了一个英雄类,可以移动和攻击"""def __init__(self, name, skill, hp, atk, armor):""" __init__() 方法,用来做变量初始化 或 赋值 操作"""# 英雄名self.name = name # 实例变量# 技能self.skill = skill# 生命值:self.hp = hp # 实例变量# 攻击力self.atk = atk# 护甲值self.armor = armordef move(self):"""实例方法"""print("%s 正在前往事发地点..." % self.name)def attack(self):"""实例方法"""print("发出了一招强力的%s..." % self.skill)# def info(self):# print("英雄 %s 的生命值 :%d" % (self.name, self.hp))# print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))# print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))def __str__(self):"""这个方法是一个魔法方法 (Magic Method) ,用来显示信息该方法需要 return 一个数据,并且只有self一个参数,当在类的外部 print(对象) 则打印这个数据"""return "英雄 <%s> 数据: 生命值 %d, 攻击力 %d, 护甲值 %d" % (self.name, self.hp, self.atk, self.armor)taidamier = Hero("泰达米尔", "旋风斩", 2600, 450, 200)
gailun = Hero("盖伦", "大宝剑", 4200, 260, 400)# 如果没有__str__ 则默认打印 对象在内存的地址。
# 当类的实例化对象 拥有 __str__ 方法后,那么打印对象则打印 __str__ 的返回值。
print(taidamier)
print(gailun)# 查看类的文档说明,也就是类的注释
print(Hero.__doc__)
说明:
- 在python中方法名如果是
__xxxx__()
的,那么就有特殊的功能,因此叫做“魔法”方法 - 当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了
__str__(self)
方法,那么就会打印从在这个方法中return
的数据 __str__
方法通常返回一个字符串,作为这个对象的描述信息
4.7 类和对象的关系
类和对象的关系是面向对象编程中的核心概念之一。简而言之,类(Class)是对象的抽象描述或模板,而对象(Object)则是根据这个模板创建的具体实例。
举个简单的例子,假设我们有一个名为“动物”的类,它描述了所有动物的共同特征,如“有生命”、“能移动”等。然后,我们可以根据这个“动物”类创建具体的对象,如“狗”、“猫”等。每只狗或猫都是一个对象,它们都具有动物类的属性和方法,但同时也有自己的特性,比如狗的品种、颜色等。
在编程中,我们通常首先定义类,然后使用这个类来创建对象。对象之间可以通过调用彼此的方法来进行交互,从而实现复杂的功能。
五、 继承
继承:见名知意,就是儿子继承父亲的方法、属性等。
- 在程序中,继承描述的是多个类之间的所属关系。
- 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
- 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。
# 父类
class Father(object):def __init__(self):self.num = 10def print_num(self):print(self.num + 10)
# 子类
class Son(A):passs = Son()
print(s.num)
s.print_num()
5.1 单继承:子类只继承一个父类
单继承是指子类只从一个父类继承。这意味着子类会获取父类的所有公共属性和方法,并可以添加或重写自己的属性和方法。
示例代码:
# 定义父类
class Parent: def __init__(self): self.parent_attribute = "我是来自父亲的属性" def parent_method(self): print("这是父亲的方法") # 定义子类,继承自Parent类
class Child(Parent): def __init__(self): # 调用父类的构造函数 super().__init__() self.child_attribute = "我是来自儿子的属性" def child_method(self): print("这是儿子的方法") # 重写父类的方法 def parent_method(self): print("这是儿子重写的父亲的方法") # 如果需要,也可以调用父类的同名方法 # super().parent_method() # 创建子类实例并测试
child_instance = Child()
print(child_instance.parent_attribute) # 继承自父类的属性
print(child_instance.child_attribute) # 子类自己的属性
child_instance.parent_method() # 子类重写的父类方法
child_instance.child_method() # 子类自己的方法
在这个例子中,我们定义了一个Parent
类,它有一个属性parent_attribute
和一个方法parent_method
。然后,我们定义了一个Child
类,它继承自Parent
类。Child
类在初始化时调用了父类的初始化方法,并添加了自己的属性child_attribute
和方法child_method
。此外,它还重写了父类的parent_method
方法。
当我们创建Child
类的一个实例child_instance
并调用其方法和属性时,我们可以看到子类既继承了父类的属性和方法,也可以有自己的属性和方法,并且还可以重写父类的方法。
5.2 多继承:子类继承多个父类
多继承是指一个子类可以继承自多个父类。这种机制为代码的重用和灵活扩展提供了强大的支持。通过多继承,子类可以继承多个父类的属性和方法,从而集成多个父类的功能。
实例代码:
# 定义第一个父类
class ParentA: def __init__(self): self.attribute_a = "我是来自ParentA的属性" def method_a(self): print("这是ParentA的方法") # 定义第二个父类
class ParentB: def __init__(self): self.attribute_b = "我是来自ParentB的属性" def method_b(self): print("这是ParentB的方法") # 定义子类,继承自ParentA和ParentB
class Child(ParentA, ParentB): def __init__(self): # 调用第一个父类的构造函数 ParentA.__init__(self) # 调用第二个父类的构造函数 ParentB.__init__(self) self.attribute_c = "我是来自Child的属性" def method_c(self): print("这是Child的方法") # 也可以重写父类的方法,如果需要的话 def method_a(self): print("这是Child类重写的ParentA的method_a方法") super().method_a() # 如果需要,也可以调用第一个父类的同名方法 # 创建子类实例并测试
child_instance = Child()
print(child_instance.attribute_a) # 继承自ParentA的属性
print(child_instance.attribute_b) # 继承自ParentB的属性
print(child_instance.attribute_c) # Child自己的属性
child_instance.method_a() # 重写的ParentA的方法
child_instance.method_b() # 继承自ParentB的方法
child_instance.method_c() # Child自己的方法
在这个例子中,Child
类同时继承了ParentA
和ParentB
两个父类。它继承了这两个父类的所有属性和方法,并添加了自己的属性和方法。同时,它还重写了ParentA
的method_a
方法。在创建Child
类的实例后,我们可以访问继承自父类的属性,调用继承或重写的方法,以及调用子类自己的方法。
需要注意的是,当子类需要调用多个父类的初始化方法时,需要显式地调用每个父类的__init__
方法。在上面的例子中,我们使用ParentA.__init__(self)
和ParentB.__init__(self)
来分别调用两个父类的初始化方法。此外,当子类需要重写父类的方法时,可以使用super()
函数来调用父类的同名方法,以确保父类的功能不会被完全覆盖。
5.3 子类重写父类的同名属性和方法
子类可以重写父类的同名属性和方法。这意味着子类可以定义与父类相同名称的属性或方法,从而覆盖父类的实现。这在许多场景中是非常有用的,比如当子类需要改变父类某个方法的行为时。
示例代码:
# 定义父类
class Parent: # 父类的同名属性 attribute = "父类的属性" # 父类的同名方法 def method(self): print("这是父类的方法") # 定义子类,继承自Parent类
class Child(Parent): # 子类重写父类的同名属性 attribute = "子类的属性" # 子类重写父类的同名方法 def method(self): print("这是子类重写后的方法") # 创建子类实例并测试
child_instance = Child() # 访问子类的属性,将看到子类重写的属性
print(child_instance.attribute) # 输出:子类的属性 # 调用子类的方法,将看到子类重写后的方法
child_instance.method() # 输出:这是子类重写后的方法 # 如果你想调用父类的同名方法(如果它没有被完全覆盖的话),可以使用super()
# 但在这个例子中,由于方法被完全重写了,调用super()将不会显示任何输出
# super(Child, child_instance).method()
在这个例子中,Parent
类有一个属性attribute
和一个方法method
。Child
类继承了Parent
类,并且重写了这两个同名成员。当我们创建Child
类的实例并访问其attribute
属性时,我们得到的是子类重写的属性值。同样地,当我们调用method
方法时,执行的是子类重写后的方法。
请注意,在重写方法时,子类通常会选择调用父类的同名方法以确保父类的功能得到正确的处理。这可以通过使用super()
函数来实现。但在上面的例子中,method
方法被完全重写了,所以调用super()
将不会执行任何操作。如果你希望保留父类方法的一部分功能,你可以在子类的方法中显式地调用super().method()
。
5.4 子类调用父类同名属性和方法
子类可以通过几种不同的方式来调用父类的同名属性和方法。当子类需要访问或调用父类的同名成员时,通常是因为子类想要扩展或修改父类的功能,而不是完全替换它。
# 定义父类
class Parent: # 父类的同名属性 attribute = "父类的属性" # 父类的同名方法 def method(self): print("这是父类的方法") # 定义子类,继承自Parent类
class Child(Parent): # 子类可以定义自己的属性,即使它与父类同名(但这会覆盖父类的属性) # attribute = "子类的属性" # 如果取消注释,将覆盖父类的attribute # 子类可以定义自己的方法,即使它与父类同名(这将覆盖父类的方法) def method(self): # 在子类中调用父类的同名方法 super().method() # 使用super()调用父类的方法 print("这是子类扩展后的方法") def another_method(self): # 在子类的另一个方法中调用父类的同名属性 print(super().attribute) # 使用super()调用父类的属性 print("这是子类中的另一个方法") # 创建子类实例并测试
child_instance = Child() # 调用子类的方法,它将调用父类的同名方法
child_instance.method()
# 输出:
# 这是父类的方法
# 这是子类扩展后的方法 # 在子类的另一个方法中调用父类的同名属性
child_instance.another_method()
# 输出:
# 父类的属性
# 这是子类中的另一个方法
在这个例子中,Child
类继承了Parent
类。Child
类重写了method
方法,并在该方法内部使用super().method()
来调用父类的同名方法。这样,子类的方法既执行了父类的代码,又添加了新的功能。在another_method
方法中,我们使用super().attribute
来访问父类的同名属性。
请注意,当子类需要访问父类的同名属性时,如果子类没有定义该属性,那么它将自动继承父类的属性。但是,如果子类定义了与父类同名的属性,那么它将覆盖父类的属性。在上面的代码中,attribute
在子类中被注释掉了,因此它不会覆盖父类的attribute
。如果取消注释,那么子类将有自己的attribute
属性,它将覆盖父类的同名属性。
同样地,当子类需要调用父类的同名方法时,如果子类没有定义该方法,那么它将自动继承父类的方法。但是,如果子类定义了与父类同名的方法,它将覆盖父类的方法。在上面的Child
类中,我们定义了method
方法,因此它覆盖了父类的method
方法。然而,通过在子类的method
方法中使用super().method()
,我们仍然能够调用父类的原始方法。
5.5 多层继承
多层继承意味着一个类可以继承自另一个已经继承自其他类的类。这种情况下,类的继承层次形成了树状结构,其中每个类都是树中的一个节点。多层继承使得代码的重用和扩展性变得更为灵活。
# 定义第一个父类
class GrandparentA: def __init__(self): self.grandparent_a_attr = "GrandparentA的属性" def grandparent_a_method(self): print("这是GrandparentA的方法") # 定义第二个父类
class GrandparentB: def __init__(self): self.grandparent_b_attr = "GrandparentB的属性" def grandparent_b_method(self): print("这是GrandparentB的方法") # 定义第一个中间类,继承自GrandparentA
class ParentA(GrandparentA): def __init__(self): super().__init__() # 调用GrandparentA的初始化方法 self.parent_a_attr = "ParentA的属性" def parent_a_method(self): print("这是ParentA的方法") super().grandparent_a_method() # 调用GrandparentA的同名方法 # 定义第二个中间类,继承自GrandparentB
class ParentB(GrandparentB): def __init__(self): super().__init__() # 调用GrandparentB的初始化方法 self.parent_b_attr = "ParentB的属性" def parent_b_method(self): print("这是ParentB的方法") super().grandparent_b_method() # 调用GrandparentB的同名方法 # 定义子类,继承自ParentA和ParentB,形成多层继承
class Child(ParentA, ParentB): def __init__(self): super().__init__() # 这会调用ParentA的初始化方法,因为ParentA在Child中排在前面 self.child_attr = "Child的属性" def child_method(self): print("这是Child的方法") def grandparent_a_method(self): print("Child类重写了GrandparentA的grandparent_a_method方法") super().grandparent_a_method() # 调用ParentA中的grandparent_a_method,进而调用GrandparentA的同名方法 # 创建子类实例并测试
child_instance = Child() # 访问继承的属性
print(child_instance.grandparent_a_attr) # 来自GrandparentA的属性
print(child_instance.grandparent_b_attr) # 来自GrandparentB的属性
print(child_instance.parent_a_attr) # 来自ParentA的属性
print(child_instance.parent_b_attr) # 来自ParentB的属性
print(child_instance.child_attr) # Child自己的属性 # 调用继承的方法
child_instance.grandparent_a_method() # Child类重写的GrandparentA的方法
child_instance.grandparent_b_method() # 继承自ParentB的GrandparentB的方法
child_instance.parent_a_method() # 继承自ParentA的方法
child_instance.parent_b_method() # 继承自ParentB的方法
child_instance.child_method() # Child自己的方法
在这个例子中,Child
类继承自ParentA
和ParentB
,而ParentA
和ParentB
又分别继承自GrandparentA
和GrandparentB
。Child
类通过多层继承获得了所有父类和祖父类中的属性和方法。同时,Child
类还重写了GrandparentA
的grandparent_a_method
方法,并在其中调用了父类ParentA
的同名方法,进而调用GrandparentA
的原始方法。这样,就形成了多层继承中方法的调用链。
需要注意的是,在多层继承中,属性的查找遵循MRO(方法解析顺序),这是一个确定的顺序列表,用于解析类继承层次结构中的方法和属性。在Python 3中,MRO是基于C3线性化算法实现的,这保证了继承的一致性和可预测性。在上述代码中,由于Child
类在定义时ParentA
在前,ParentB
在后,因此super().__init__()
会首先调用ParentA
的__init__
方法。
5.6 通过super()来调用父类中的方法
super()
函数用于调用父类(超类)中的一个方法。这在你想要扩展或修改父类中的方法时特别有用,同时你仍然希望保留父类方法的一些功能。
# 定义父类
class Parent: def my_method(self): print("这是父类的方法") # 定义子类,继承自Parent类
class Child(Parent): def my_method(self): # 使用super()调用父类的my_method方法 super().my_method() print("这是子类扩展后的方法") # 创建子类实例
child_instance = Child() # 调用子类的方法,它将先调用父类的方法,再执行子类的扩展代码
child_instance.my_method()
在这个例子中,Child
类继承了Parent
类,并且重写了my_method
方法。在Child
类的my_method
方法中,我们使用super().my_method()
来调用父类中的my_method
方法。这样,当Child
类的my_method
被调用时,它会首先执行父类中的方法,然后再执行子类中添加的代码。
注意,当你使用super()
时,Python会根据方法解析顺序(MRO)自动找到正确的父类。在大多数情况下,你不需要(也不应该)直接指定父类,而是应该使用super()
,因为它能够正确处理复杂的继承情况,比如多重继承。
六、封装
6.1 封装的意义
- 将属性和方法放到一起做为一个整体,然后通过实例化对象来处理;
- 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了;
- 对类的属性和方法增加 访问权限控制。
6.2 私有权限:在属性名和方法名 前面 加上两个下划线 __
- 类的私有属性 和 私有方法,都不能通过对象直接访问,但是可以在本类内部访问;
- 类的私有属性 和 私有方法,都不会被子类继承,子类也无法访问;
- 私有属性 和 私有方法 往往用来处理类的内部事情,不通过对象处理,起到安全作用。
示例代码:
class MyClass: def __init__(self): # 私有属性 self.__private_attribute = "这是一个私有属性" def public_method(self): # 调用私有方法 self.__private_method() print("这是一个公开方法") def __private_method(self): # 私有方法 print("这是一个私有方法") def get_private_attribute(self): # 提供一个公开的方法来获取私有属性的值 return self.__private_attribute # 创建类的实例
obj = MyClass() # 尝试直接访问私有属性,这会引发AttributeError
# print(obj.__private_attribute) # 这行会报错 # 调用公开方法
obj.public_method() # 通过公开方法获取私有属性的值
print(obj.get_private_attribute())
总结:
- Python中没有像C++中 public 和 private 这些关键字来区别公有属性和私有属性。
- Python是以属性命名方式来区分,如果在属性和方法名前面加了2个下划线'__',则表明该属性和方法是私有权限,否则为公有权限。
6.3 修改私有属性的值
-
如果需要修改一个对象的属性值,通常有2种方法
- 对象名.属性名 = 数据 ----> 直接修改
- 对象名.方法名() ----> 间接修改
-
私有属性不能直接访问,所以无法通过第一种方式修改,一般的通过第二种方式修改私有属性的值:定义一个可以调用的公有方法,在这个公有方法内访问修改。
示例代码:
第一种:直接修改
class MyClass: def __init__(self): # 私有属性 self.__private_attribute = "初始值" def show_private_attribute(self): # 展示私有属性的值 print(self.__private_attribute) # 创建类的实例
obj = MyClass() # 直接修改私有属性(不推荐)
obj._MyClass__private_attribute = "直接修改后的值" # 展示修改后的私有属性值
obj.show_private_attribute() # 输出: 直接修改后的值
第二种:间接修改
class MyClass: def __init__(self): # 私有属性 self.__private_attribute = "初始值" def set_private_attribute(self, value): # 公共setter方法用于间接修改私有属性的值 self.__private_attribute = value def get_private_attribute(self): # 公共getter方法用于获取私有属性的值 return self.__private_attribute def show_private_attribute(self): # 展示私有属性的值 print(self.get_private_attribute()) # 创建类的实例
obj = MyClass() # 间接修改私有属性(推荐)
obj.set_private_attribute("间接修改后的值") # 展示修改后的私有属性值
obj.show_private_attribute() # 输出: 间接修改后的值
七、多态
7.1 什么是多态?
在需要使用父类对象的地方,也可以使用子类对象, 这种情况就叫多态.
比如, 在函数中,我需要调用 某一个父类对象的方法, 那么我们也可以在这个地方调用子类对象的方法.
7.2 如何在程序中使用多态
可以按照以下几个步骤来写代码:1.子类继承父类2.子类重写父类中的方法3.通过对象调用这个方法
示例代码:
# 定义一个基类
class Animal: def speak(self): pass # 定义子类,继承自Animal,并重写speak方法
class Dog(Animal): def speak(self): return "汪汪汪" class Cat(Animal): def speak(self): return "喵喵喵" # 定义一个函数,接受一个Animal类型的参数
def animal_speak(animal): print(animal.speak()) # 创建Dog和Cat的实例,并调用animal_speak函数
dog = Dog()
cat = Cat() animal_speak(dog) # 输出:汪汪汪
animal_speak(cat) # 输出:喵喵喵
在这个例子中,我们定义了一个基类Animal
,它有一个speak
方法,但没有具体的实现。然后我们定义了两个子类Dog
和Cat
,它们都继承自Animal
,并重写了speak
方法。接着我们定义了一个函数animal_speak
,它接受一个Animal
类型的参数,并调用其speak
方法。最后我们创建了一个Dog
实例和一个Cat
实例,并分别调用animal_speak
函数。由于Dog
和Cat
都重写了speak
方法,所以它们的行为不同,这就是多态的体现。
7.3 多态的好处
-
代码重用和扩展性:多态允许我们编写通用的代码来处理不同类型的对象,而无需为每个类型编写特定的代码。这大大减少了代码量,提高了代码的重用性。同时,当需要添加新的子类时,我们只需确保新的子类遵循相同的接口或继承相同的父类,就可以轻松地将它们集成到现有的多态代码中,无需修改其他部分的代码。
-
解耦:多态有助于降低代码之间的耦合度。由于多态允许使用父类类型的引用来操作子类对象,因此我们可以将具体的实现细节隐藏在子类中,只暴露必要的接口给父类。这样,上层代码只需与父类接口交互,而无需关心具体的子类实现,从而降低了代码的耦合度,提高了代码的可维护性和灵活性。
-
灵活性:多态允许我们在运行时动态地确定实际要调用的方法。这意味着我们可以在不修改现有代码的情况下,通过替换子类实现来改变程序的行为。这种灵活性使得程序更加易于适应不同的需求和场景。
-
符合开闭原则:多态的设计思想符合面向对象编程的开闭原则,即对扩展开放,对修改封闭。通过多态,我们可以在不修改现有代码的情况下添加新的功能或修改现有功能的行为,从而降低了代码维护的难度和成本。