Python类和对象(下) python复习笔记

5. 封装 enclosure

  • 封装是指隐藏类的实现细节,让使用者不用关心这些细节;

  • 封装的目的是让使用者通过尽可能少的方法(或属性)操作对象

  • Python的封装是假的(模拟的)封装

  • 私有属性和方法

    • python类中以双下划线(__)开头,不以双下划线结尾的标识符为私有成员,私有成员只能使用方法来进行访问和修改

      • __开头的属性为类的私有属性,在子类和类外部无法直接使用

      • __开头的方法为私有方法,在子类和类外部无法直接调用

  • 私有属性和方法示例:

    ​
    class A:
        def __init__(self):
            self.__p1 = 100  # 私有属性
        def __m1(self):  # 私有方法
            print("__m1(self) 方法被调用")
        def showA(self):
            self.__m1()
            print("self.__p1 = ", self.__p1)
    class B(A):
        def __init__(self):
            super().__init__()
        def showB(self):
            self.__m1()  # 出错,不允许调用
            print("self.__p1 = ", self.__p1)  # 出错,不允许调用
            # self._A__m1()  # 正常调用
            # print("self.__p1 =", self._A__p1)  # 正常访问
    ​
    a = A()
    a.showA()
    a.__m1()       # 出错,不允许调用
    v = self.__p1  # 出错,不允许调用
    b = B()
    b.showB()
    ​
    # 访问私有属性
    print(a._A__p1)  # 输出: 100
    # 调用私有方法
    a._A__m1()  # 输出: __m1(self) 方法被调用
    # 不推荐了解就行

6. 多态 polymorphic

  • 什么是多态:

    • 字面意思"多种状态"

    • 多态是指在有继承/派生关系的类中,调用基类对象的方法,实际能调用子类的覆盖方法的现象叫多态

  • 状态:

    • 静态(编译时状态)

    • 动态(运行时状态)

  • 多态说明:

    • 多态调用的方法与对象相关,不与类型相关

    • Python的全部对象都只有"运行时状态(动态)", 没有"C++语言"里的"编译时状态(静态)"

  • 多态示例:

    class Shape:
        def draw(self):
            print("Shape的draw()被调用")
    ​
    class Point(Shape):
        def draw(self):
            print("正在画一个点!")
    ​
    class Circle(Point):
        def draw(self):
            print("正在画一个圆!")
    ​
    def my_draw(s):
        s.draw()  # 此处显示出多态
    ​
    shapes1 = Circle()
    shapes2 = Point()
    my_draw(shapes1)  # 调用Circle 类中的draw
    my_draw(shapes2)  # Point 类中的draw
  • 面向对象编程语言的特征:

    • 继承

    • 封装

    • 多态

7. 方法重写

果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法

函数重写

  • 在自定义类内添加相应的方法,让自定义类创建的实例像内建对象一样进行内建函数操作

对象转字符串函数重写
  • 对象转字符串函数重写方法

    • str() 函数的重载方法:

      • def __str__(self)

        • 如果没有 __str__(self) 方法,则返回repr(obj)函数结果代替

  • str/repr函数重写示例

    class MyNumber:
        "此类用于定义一个自定义的类,用于演示str/repr函数重写"
        def __init__(self, value):
            "构造函数,初始化MyNumber对象"
            self.data = value
        def __str__(self):
            "转换为普通字符串"
            return "%s" % self.data
    ​
    n1 = MyNumber("一只猫")
    n2 = MyNumber("一只狗")
    print("str(n2) ===>", str(n2))
内建函数重写
  • __abs__ abs(obj) 函数调用

  • __len__ len(obj) 函数调用

  • __reversed__ reversed(obj) 函数调用

  • __round__ round(obj) 函数调用

  • 内建函数 重写示例

    # file : len_overwrite.py
    class MyList:
        def __init__(self, iterable=()):
            self.data = [x for x in iterable]
        def __repr_(self):
            return "MyList(%s)" % self.data
        def __len__(self):
            print("__len__(self) 被调用!")
            return len(self.data)
        def __abs__(self):
            print("__len__(self) 被调用!")
            return MyList((abs(x) for x in self.data))
    ​
    myl = MyList([1, -2, 3, -4])
    print(len(myl))
    print(abs(myl))

运算符重载

  • 运算符重载是指让自定义的类生成的对象(实例)能够使用运算符进行操作

  • 运算符重载的作用

    • 让自定义类的实例像内建对象一样进行运算符操作

    • 让程序简洁易读

    • 对自定义对象将运算符赋予新的运算规则

  • 运算符重载说明:

    • 运算符重载方法的参数已经有固定的含义,不建议改变原有的意义

算术运算符重载
方法名 运算符和表达式 说明
__add__(self, rhs) self + rhs 加法
__sub__(self, rhs) self - rhs 减法
__mul__(self, rhs) self * rhs 乘法
__truediv__(self, rhs) self / rhs 除法
__floordiv__(self, rhs) self // rhs 地板除
__mod__(self, rhs) self % rhs 取模(求余)
__pow__(self, rhs) self ** rhs
rhs (right hand side) 右手边
  • 二元运算符重载方法格式:

    def __xxx__(self, other):
        ....
  • 算术运算符重载示例

    class MyNumber:
        "此类用于定义一个自定义的类,用于演示运算符重载"
        def __init__(self, value):
            "构造函数,初始化MyNumber对象"
            self.data = value
        def __str__(self):
            "转换为表达式字符串"
            return "MyNumber(%d)" % self.data
        def __add__(self, rhs):
            "加号运算符重载"
            print("__add__ is called")
            return MyNumber(self.data + rhs.data)
        def __sub__(self, rhs):
            "减号运算符重载"
            print("__sub__ is called")
            return MyNumber(self.data - rhs.data)
    ​
    n1 = MyNumber(100)
    n2 = MyNumber(200)
    print(n1 + n2)
    print(n1 - n2)

8. super函数

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

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

super() 方法的语法:

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

可以使用super(Child, obj).myMethod()用子类对象调用父类已被覆盖的方法

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
class Parent:        # 定义父类
   def myMethod(self):
      print ('调用父类方法')
 
class Child(Parent): # 定义子类
   def myMethod(self):
      print ('调用子类方法')
 
c = Child()# 子类实例
c.myMethod()# 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法

super().init()

super().__init__() 是 Python 中用于调用父类(基类)构造函数的一种方式。它通常用于子类的构造函数中,以确保父类的构造函数被正确调用和初始化。这在继承(inheritance)中尤为重要,因为父类的初始化代码可能包含设置实例变量或执行其他重要的初始化任务。

class Parent:
    def __init__(self):
        print("Parent class constructor called")
        self.parent_attribute = "I am a parent attribute"
​
class Child(Parent):
    def __init__(self):
        super().__init__()
        print("Child class constructor called")
        self.child_attribute = "I am a child attribute"
​
# 创建一个 Child 类的实例
child_instance = Child()
print(child_instance.parent_attribute)
​
# 输出
# Parent class constructor called
# Child class constructor called
​

注释

  1. Parent 类

    • 定义了一个构造函数 __init__(),在构造函数中打印了一条消息,并初始化了一个属性 parent_attribute

  2. Child 类

    • 继承自 Parent 类。

    • 在其构造函数 __init__() 中,首先调用了 super().__init__()。这行代码会调用 Parent 类的构造函数,确保 Parent 类的初始化逻辑被执行。

    • 然后打印了一条消息,并初始化了一个属性 child_attribute

  3. 实例化 Child 类

    • 创建 Child 类的实例时,首先执行 Parent 类的构造函数,打印 "Parent class constructor called",然后执行 Child 类的构造函数,打印 "Child class constructor called"。

为什么使用 super().__init__()

  • 代码重用:避免在子类中重复父类的初始化代码。

  • 正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。

  • 支持多重继承:在多重继承情况下,super() 可以确保所有基类的构造函数都被正确调用。

举例

class Attention(nn.Module):
    def __init__(self, dim, heads=8, dim_head=64, dropout=0.):
        super().__init__()
        inner_dim = dim_head * heads  # 计算内部维度
        project_out = not (heads == 1 and dim_head == dim)  # 判断是否需要投影输出
​

在之后的深度学习方法中经常见 super().__init__()的用法