python进阶语法day9

1.继承/派生

  • 什么是继承/派生

    • 继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。

    • 派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为

  • 为什么继承/派生

    • 继承的目的是延续旧的类的功能

    • 派生的目地是在旧类的基础上添加新的功能

  • 继承/派生的作用

    • 用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。

    • 在不改变基类的代码的基础上改变原有类的功能

  • 继承/派生名词:

    • 基类(base class)/超类(super class)/父类(father class)

    • 派生类(derived class)/子类(child class)

1.1 单继承

  • 单继承的语法:

  • class 类名(基类名):
        语句块

  • 单继承说明:单继承是指派生类由一个基类衍生出来的

  • class Human:  # 人类的共性
        def say(self, what):  # 说话
            print("说:", what)
        def walk(self, distance):  # 走路
            print("走了", distance, "公里")
    
    class Student(Human):
        def study(self, subject):  # 学习
            print("学习:", subject)
    
    class Teacher(Human):
        def teach(self, language):
            print("教:", language)
    
    h1 = Human()
    h1.say("天气真好!")
    h1.walk(5)
    
    s1 = Student()
    s1.walk(4)
    s1.say("感觉有点累")
    s1.study("python")
    
    t1 = Teacher()
    t1.teach("面向对象")
    t1.walk(6)
    t1.say("一会吃点什么好呢")

    Python支持多继承形式。多继承的类定义形如下例:

    class DerivedClassName(Base1, Base2, Base3):
        <statement-1>
        .
        .
        .
        <statement-N>

    需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

  • # 类定义
    class people:
        # 定义基本属性
        name = ''
        age = 0
        # 定义私有属性,私有属性在类外部无法直接进行访问
        __weight = 0
    
        # 定义构造方法
        def __init__(self, n, a, w):
            self.name = n
            self.age = a
            self.__weight = w
    
        def speak(self):
            print("%s 说: 我 %d 岁。" % (self.name, self.age))
    
    
    # 另一个类,多继承之前的准备
    class speaker():
        topic = ''
        name = ''
    
        def __init__(self, n, t):
            self.name = n
            self.topic = t
    
        def speak(self):
            print("我叫 %s,我是一个演说家,我演讲的主题是 %s" % (self.name, self.topic))
    
    
    # 多继承
    class sample(people, speaker):
    
        def __init__(self, n, a, w, t):
            people.__init__(self, n, a, w)
            speaker.__init__(self, n, t)
    
    
    test = sample("Tim", 25, 80, "Python")
    test.speak()  # 方法名同,默认调用的是在括号中参数位置排前父类的方法

    4.2 多继承

    Python支持多继承形式。多继承的类定义形如下例:

    class DerivedClassName(Base1, Base2, Base3):
        <statement-1>
        .
        .
        .
        <statement-N>

    需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。

  • # 类定义
    class people:
        # 定义基本属性
        name = ''
        age = 0
        # 定义私有属性,私有属性在类外部无法直接进行访问
        __weight = 0
    
        # 定义构造方法
        def __init__(self, n, a, w):
            self.name = n
            self.age = a
            self.__weight = w
    
        def speak(self):
            print("%s 说: 我 %d 岁。" % (self.name, self.age))
    
    
    # 另一个类,多继承之前的准备
    class speaker:
        topic = ''
        name = ''
    
        def __init__(self, n, t):
            self.name = n
            self.topic = t
    
        def speak(self):
            print("我叫 %s,我是一个演说家,我演讲的主题是 %s" % (self.name, self.topic))
    
    
    # 多继承
    class sample(people, speaker):
    
        def __init__(self, n, a, w, t):
            people.__init__(self, n, a, w)
            speaker.__init__(self, n, t)
    
    
    test = sample("Tim", 25, 80, "Python")
    test.speak()  # 方法名同,默认调用的是在括号中参数位置排前父类的方法

    4.3 覆盖 override

  • 覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫覆盖

  • 作用:

    • 实现和父类同名,但功能不同的方法

  • 覆盖示例

    class A:
        def work(self):
            print("A.work 被调用!")
    
    class B(A):
        '''B类继承自A类'''
        def work(self):
            print("B.work 被调用!!!")
        pass
    
    b = B()
    b.work()  # 请问调用谁?  B
    
    a = A()
    a.work()  # 请问调用谁?  A

    2. 封装 enclosure

    封装(Encapsulation) 是一种将数据(属性)和操作数据的方法(方法)绑定在一起的机制。封装的主要目的是隐藏对象的内部状态,并对外部提供有限的访问接口。通过封装,可以防止外部代码直接访问对象的内部数据,从而提高代码的安全性和可维护性。

    在 Python 中,封装主要通过以下几种方式实现:

    1. 私有属性(Private Attributes)

    在 Python 中,没有严格的私有属性,但可以通过命名约定来模拟私有属性。通常,使用单下划线 _ 或双下划线 __ 来表示私有属性。

    单下划线 _

    单下划线前缀表示属性是“受保护的”,建议外部代码不要直接访问。

    单下划线前缀 _ 是一种约定,而不是语言层面的强制规则。这种约定并没有强制性,外部代码仍然可以直接访问这些属性或方法。

  • class A:
        def __init__(self,value):
            self._value=value
    a=A(5)
    a._value=2
    print(a._value)

    双下划线 __

    双下划线前缀表示属性是“私有的”,Python 会对其进行名称改写(name mangling),使其在类外部难以直接访问。

  • class MyClass:
        def __init__(self):
            self.__private_attr = 43
    
    obj = MyClass()
    print(obj.__private_attr)

    示例运行会报错:

    AttributeError: 'MyClass' object has no attribute '__private_attr'

    这是因为私有变量被python修改了名称,名称改写的规则是将属性名前面加上类名和一个下划线 _。假设类 MyClass,其中有一个私有属性 __private_attr,那么 Python 会将这个属性名改写为 _MyClass__private_attr

  • class MyClass:
        def __init__(self):
            self.__private_attr = 43
    
    obj = MyClass()
    print(obj._MyClass__private_attr)

    obj._MyClass__private_attr:直接访问python修改后的私有变量名称可以成功,但不建议这么访问,主要是为了防止子类意外覆盖父类的私有属性。

    2. 属性访问器(Getter 和 Setter)

    单下划线和双下划线的变量虽然可以直接或间接被访问,但python不建议这么做。

    如果需要访问或修改这些属性,应该通过类提供的公共方法gettersetter ,来控制对属性的访问和修改

  • class MyClass:
        def __init__(self):
            self._private_attr = 43
    
        def get_attr(self):
            return self._private_attr
        def set_attr(self, attr):
            self._private_attr = attr
    
    a = MyClass()
    print(a.get_attr())
    print(a.set_attr(12))
    print(a.get_attr())
    
    
    43
    None
    12

    3. 使用 @property 装饰器

    Python 提供了 @property 装饰器,可以更简洁地实现属性的访问和修改。

    @property是 Python 中的一个装饰器,用于将类的方法转换为属性。@property 装饰器用于定义一个只读属性。通过使用 @property,可以将方法的调用方式转换为属性的访问方式,从而使代码更加简洁和易读。

    如果需要设置属性的值,可以使用 @property.setter 装饰器定义一个可写属性

    @property 通常与 @<property_name>.setter 一起使用,以实现属性的读取和写入操作。

  • class A:
        def __init__(self,value):
            self._protec_value=value
        @property
        def propet(self):
            return self._protec_value
        @propet.setter
        def propet(self,value):
             self._protec_value=value
    a = A(10)
    print(a.propet)
    a.propet = 18
    print(a.propet)

    3. 多态 polymorphic

    多态(Polymorphism)是指同一个方法在不同的类中有不同的实现。多态可以分为两种主要类型:静态多态(Static Polymorphism)和动态多态(Dynamic Polymorphism)。

    3.1 静态多态

    静态多态也称为编译时多态(Compile-time Polymorphism),主要通过以下两种方式实现:

    3.1.1 方法重载

    方法重载是指在同一个类中定义多个同名但参数不同的方法。编译器根据调用时传递的参数类型和数量来决定调用哪个方法。

  • class calculeter:
        def add(self,a,b):
            return a+b
        def add(self,a,b,c):
            return a+b+c
    
    c = calculeter()
    print(c.add(10,20))
    # 在 Python 中,方法重载并不直接支持,因为 Python 是动态类型语言
    # 但可以通过默认参数或可变参数来模拟方法重载
    class calculeter:
        def add(self,a,b,c=0):
            return a+b+c
    c = calculeter()
    print(c.add(10,20,30))
    3.1.2 运算符重载

    运算符重载(Operator Overloading) 是指通过定义类的魔术方法(Magic Methods),使类的对象支持 Python 的内置运算符(如 +-*/ 等)。通过运算符重载,我们可以让自定义类的对象像内置类型(如整数、字符串、列表等)一样使用运算符。

    方法名运算符说明
    __add__(self, rhs)+加法
    __sub__(self, rhs)-减法
    __mul__(self, rhs)*乘法
    __truediv__(self, rhs)/除法
    __floordiv__(self, rhs)//整除
    __mod__(self, rhs)%取模(求余)
    __pow__(self, rhs)**
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)
    def __str__(self):
        dic = {'x': self.x, 'y': self.y}
        return f'{dic}'

p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3)


{'x': 4, 'y': 6}

 

3.2 动态多态

动态多态也称为运行时多态(Runtime Polymorphism),主要通过方法重写(Method Overriding)实现。

3.2.1 方法重写

方法重写是指子类重新定义父类中已有的方法,从而在运行时根据对象的实际类型来调用相应的方法。

示例:假设基类为Animal,拥有一个speak方法,Dog和Cat分别是Animal的子类,继承Animal的方法

class Animal:
    def speak(self):
        return '通用'

class Dog(Animal):
    def speak(self):
        return '狗叫'
class Cat(Animal):
    def speak(self):
        return '猫叫'
dog = Dog()
print(dog.speak())


狗叫
3.2.3 抽象基类

Python 提供了 abc 模块,用于定义抽象基类。抽象基类不能直接实例化,而是作为其他类的模板。子类必须实现抽象基类中定义的抽象方法,否则会引发错误

抽象基类的作用
定义接口规范:

抽象基类可以定义一组方法(接口),要求所有子类必须实现这些方法。

这样可以确保所有子类都遵循相同的接口规范,避免遗漏或错误。

强制子类实现:

如果子类没有实现抽象基类中定义的所有抽象方法,Python 会在实例化时抛出 TypeError。

这种机制可以避免子类忘记实现某些关键方法。

提高代码可读性和可维护性:

抽象基类明确地告诉开发者,哪些方法是必须实现的,哪些是可选的。

这样可以让代码的结构更清晰,减少潜在的错误。

支持类型检查:

使用抽象基类可以通过 isinstance() 和 issubclass() 检查对象是否符合特定的接口规范。

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# 多态的体现
def animal_sound(animal):
    print(animal.speak())

dog = Dog()
cat = Cat()

animal_sound(dog)  # 输出: Woof!
animal_sound(cat)  # 输出: Meow!

4. super函数

4.1 MRO查找顺序

MRO(Method Resolution Order,方法解析顺序)描述了 Python 在多重继承的情况下,如何查找类的方法和属性。MRO 决定了当一个类继承自多个父类时,Python 如何确定方法和属性的查找顺序。

MRO 的计算规则

Python 使用 C3 线性化算法来计算 MRO。C3 线性化算法确保了以下几点:

  1. 子类优先于父类:子类的方法或属性会优先于父类被查找。

  2. 父类的顺序保持不变:在定义类时,父类的顺序会影响 MRO。在多重继承中,按照继承列表的顺序从左到右查找。

  3. 单调性:如果一个类出现在另一个类的 MRO 中,那么它的父类也应该出现在这个类的 MRO 中。

MRO 的计算方法

MRO 的计算方法可以通过 __mro__ 属性或 mro() 方法来查看。

class A:
    def method(self):
        print("A.method")

class B(A):
    def method(self):
        print("B.method")

class C(A):
    def method(self):
        print("C.method")

class D(B, C):
    pass

# 查看 D 类的 MRO
print(D.__mro__)


#输出:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
class A:
    def method(self):
        print("A.method")

class B(A):
    def method(self):
        print("B.method")

class C(A):
    def method(self):
        print("C.method")

class D(B, C):
    def method(self):
        print("D.method")

class E(C, B):
    def method(self):
        print("E.method")

class F(D, E):
    pass

# 查看 F 类的 MRO
print(F.__mro__)
  1. D 的 MRO

    • D 继承自 BC

    • B 继承自 A

    • C 继承自 A

    • 因此,D 的 MRO 顺序是 D -> B -> C -> A -> object

  2. E 的 MRO

    • E 继承自 CB

    • C 继承自 A

    • B 继承自 A

    • 因此,E 的 MRO 顺序是 E -> C -> B -> A -> object

  3. F 的 MRO

    • F 继承自 DE

    • D 的 MRO 顺序是 D -> B -> C -> A -> object

    • E 的 MRO 顺序是 E -> C -> B -> A -> object

问题原因

  • DE 的继承关系导致了循环依赖,无法满足 C3 线性化算法的单调性、一致性和局部优先级规则。

  • 具体来说,D 的 MRO 顺序是 D -> B -> C -> A -> object,而 E 的 MRO 顺序是 E -> C -> B -> A -> object

  • 这导致了 F 无法创建一致的 MRO,因为 BC 的顺序在 DE 中不一致。

在这种情况下,Python无法创建一个一致的方法解析顺序(MRO),解决办法:类D和E继承B、C的顺序一致,即:

4.2 super()

super() 函数是用于调用父类(超类)的一个方法。

super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

super() 是一个用于调用父类方法的函数,它依赖于 Python 的 MRO 机制。因此,super() 本身并不能解决 MRO 冲突,而是依赖于已经确定的 MRO。

super() 解决多继承问题的关键在于 MRO(方法解析顺序),即 Python 按照某种顺序(通常是从左到右)依次调用继承链中的方法。super() 总是会调用下一个类的 process(),而不会重复调用同一个类的方法。

使用 super() 时,Python 会动态查找类继承链中的下一个类。通过 MRO,避免了手动调用时可能发生的重复调用。

super() 方法的语法:

super() 的无参数形式:

在子类方法中可以使用super().add()调用父类中已被覆盖的方法

class A:
     def add(self, x):
         y = x+1
         print(y)
class B(A):
    def add(self, x):
        print("子类方法")
        super().add(x)
b = B()
b.add(2)  # 3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值