Python——方法类型和继承相关笔记


Python 几种方法区别

  • Python 中包含三种方法:实例方法类方法(@classmethod)静态方法(@staticmethod)
    • 实例方法 :围绕“对象”(依赖 self
    • 类方法 :围绕“类”(依赖 cls
      • 常用于以下使用场景:
        • 1)需要操作类属性
        • 2)需要实现工厂模式(替代构造函数)
    • 静态方法 :放在类里的“普通函数”(不依赖任何绑定)
      • 当在方法体内不需要使用 selfcls ,用 @staticmethod 可以清晰地告诉阅读者:“这个方法与类的内部状态无关”
  • 总结:
    类型 装饰器 第一个参数名(约定) 绑定对象 能否访问实例属性 能否访问类属性
    实例方法 self 实例对象 能(通过 self.__class__
    类方法 @classmethod cls 类本身 不能 能(通过 cls
    静态方法 @staticmethod 无(普通函数) 不绑定任何对象 不能 能(但建议直接 类名.属性

Python 继承体系下的本质区别理解

  • 实例方法与类方法 :自动传递 selfcls
    • 无论是 self 还是 cls,Python 都会自动将调用者(实际的子类或子类实例)作为第一个参数传入
      • 注意:是实际的子类或子类实例,而不是自身,也就是说,无论如何,使用 super.method() 调用 时,self/cls 都是子类的,不是父类的
    • 但是方法本身是父类的,不是子类的,只是传入的特殊参数本身是子类的
  • 静态方法 :缺少 “自我认知”
    • 静态方法不接收 selfcls,因此它在代码层面无法感知自己被哪个类调用
    • 如果在静态方法内部硬编码了父类名,子类调用时无法实现多态
  • 代码示例 :不同方法多态性的差异
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Parent:
    tag = "Parent"

    @classmethod
    def get_tag_cls(cls):
    return cls.tag # 动态获取(类方法多态)

    @staticmethod
    def get_tag_static():
    return Parent.tag # 硬编码,写死了(不支持多态)

    # 新增:普通实例方法,通过 self 动态获取 tag(支持多态)
    def get_tag_instance(self):
    return self.tag # self 指向实际调用对象,运行时动态绑定

    class Child(Parent):
    tag = "Child"

    # 测试调用
    print(Child.get_tag_cls()) # 输出: Child (类方法动态绑定)
    print(Child.get_tag_static()) # 输出: Parent (静态方法硬编码)
    print(Child().get_tag_instance()) # 输出: Child (实例方法动态绑定,多态表现)

特别补充: super() 的关键机制(常常容易易出错)

  • 核心原则super() 只负责沿着 MRO(方法解析顺序)查找父类中的方法 ,但不会改变传入的第一个参数(selfcls)的指向

    • 理解:调用者(最早一个 super() 运行的地方)是谁,传入的就是谁
    • 特别说明:无论 super() 函数在复杂的继承链条中被调用多少次,它所传递的 self(对于实例方法)或 cls(对于类方法),始终是原始发起调用的那个 “最底层”子类 的实例或类本身
  • 类方法传递情况示例(实例方法也是类似的)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Parent:
    @classmethod
    def create(cls):
    print(f"父类方法中的 cls: {cls.__name__}") # 输出:实际调用者的类名(不管多少层继承)
    return cls()

    class Child(Parent):
    @classmethod
    def create(cls):
    return super().create() # super().create() 函数的 cls 参数依然是 Child

    obj = Child.create()
    print(type(obj).__name__) # 输出: Child (成功创建子类实例)

    # 父类方法中的 cls: Child
    # Child
  • 场景二:实例方法中使用 super().init 或普通方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Parent:
    def who(self):
    print(f"父类中的 self: {type(self).__name__}") # 输出:实际调用者的类名(不管多少层继承)

    class Child(Parent):
    def who(self):
    super().who() # super().who() 函数的 self 参数依然是 Child 实例

    Child().who()
    # 父类中的 self: Child
  • 特殊的示例:菱形继承下的对象也总是最早调用的那个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class A:
    def __init__(self):
    print(f"A 收到 self: {id(self)},类型: {type(self)}")

    class B(A):
    def __init__(self):
    print(f"B 收到 self: {id(self)},类型: {type(self)}")
    super().__init__() # 在 B 中调用 super

    class C(A):
    def __init__(self):
    print(f"C 收到 self: {id(self)},类型: {type(self)}")
    super().__init__() # 在 C 中调用 super

    class D(B, C):
    def __init__(self):
    print(f"C 收到 self: {id(self)},类型: {type(self)}")
    super().__init__() # 在 D 中发起调用

    d = D()

    # C 收到 self: 4317027024,类型: <class '__main__.D'>
    # B 收到 self: 4317027024,类型: <class '__main__.D'>
    # C 收到 self: 4317027024,类型: <class '__main__.D'>
    # A 收到 self: 4317027024,类型: <class '__main__.D'>
    • 理解:即使执行的是父类中的代码,selfcls 依然保持为最底层的子类
      • 这也是 Python 实现多态的底层保障

补充:@staticmethod 的继承误区澄清

  • 注:我们可能会错误的以为 @staticmethod 不支持继承,其实是支持的
  • @staticmethod 支持继承和重写
  • 继承代码复用示例 :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Parent:
    @staticmethod
    def add(x, y):
    return x + y

    class Child(Parent):
    pass

    print(Child.add(1, 2)) # 输出: 3 (完全没问题)

Python 继承和 Java 的区别

  • 设计哲学 区别:
    • Java :严格语法、编译时强制,构建安全可预测的结构化体系
      • 编译时强制契约(适合大型企业级应用)
    • Python :动态灵活、简洁优先,追求开发效率与运行时自由
      • 运行时动态协议(适合快速迭代与原型开发)
  • 两者对比:
    对比维度 Python Java
    继承模型 支持多继承(C3线性化 + MRO 解决菱形问题) 单继承 ,但可 implements 多个接口
    访问控制 __(私有,名称修饰强制);_(受保护,仅命名约定,不强制);默认公有 privateprotectedpublic编译器强制执行
    方法类型与重写 实例方法、@classmethod@staticmethod 均可被子类重写 实例方法可重写(多态);static 方法只能继承和隐藏(不可重写)
    构造函数调用 子类 __init__ 不自动调用父类,需显式 super().__init__() 子类构造函数必须调用父类构造(隐式默认或显式 super,且为第一句)
    抽象与接口 通过 abc 模块实现抽象基类,语法不区分抽象类和接口;推崇鸭子类型 语法明确区分 abstract classinterface(静态契约)

多态本质的理解

  • 多态的本质是:同一个方法名,在不同的对象上表现出不同的行为
  • 理解:Python 和 Java 两者都可以实现多态(即 “同一方法名,不同行为”)
    • Python 的多态更彻底,因为它不受继承体系的束缚
    • Java 的多态更严谨,但要求事先定义好契约(接口或父类)

Python 实现多态(鸭子类型,不依赖继承)

  • Python 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Dog:
    def speak(self): return "汪汪"

    class Cat:
    def speak(self): return "喵喵"

    class Car: # 和 Dog/Cat 毫无继承关系
    def speak(self): return "嘟嘟"

    def talk(obj):
    print(obj.speak()) # 多态调用:只要对象有 speak 即可

    talk(Dog()) # 汪汪
    talk(Cat()) # 喵喵
    talk(Car()) # 嘟嘟 ← 无需任何继承,纯动态多态

Java 实现(继承或接口,必须基于类型体系)

  • Java 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // 方式1:使用接口(推荐,更灵活)
    interface Speaker {
    String speak();
    }

    class Dog implements Speaker {
    public String speak() { return "汪汪"; }
    }

    class Cat implements Speaker {
    public String speak() { return "喵喵"; }
    }

    // 方式2:使用父类(类似)
    // class Animal { String speak() {...} }
    // class Dog extends Animal {...}

    public class Main {
    static void talk(Speaker obj) { // 必须声明父类/接口类型
    System.out.println(obj.speak());
    }

    public static void main(String[] args) {
    talk(new Dog()); // 汪汪
    talk(new Cat()); // 喵喵
    // talk(new Car()); // 编译错误,Car 必须实现 Speaker
    }
    }