Python面向对象深度探索:对比Java,详解类、继承、self与魔法方法

kayokoi 发布于 2025-05-28 50 次阅读


Python 是一种支持多种编程范式的语言,面向对象编程 (OOP) 是其核心特性之一。对于有Java OOP经验的开发者来说,理解Python在类、对象、继承和多态等方面的独特实现方式,将有助于更好地运用Python进行设计和开发。

一、Python类定义基础

<a name="1-类定义-class-definition"></a>

1.1 基本语法与核心组成

在Python中,使用 class 关键字定义类,其后跟类名和可选的父类列表(用于继承),然后是冒号和缩进的类体。

class MyClassName(BaseClass1, BaseClass2, ...): # Python支持多重继承
    """可选的类文档字符串 (Docstring)"""
    class_attribute = "我是类属性,所有实例共享" # 类属性

    def __init__(self, param1, param2): # 初始化方法 (构造器)
        self.instance_attribute1 = param1 # 实例属性,通过self绑定到实例
        self.instance_attribute2 = param2

    def instance_method(self, other_param): # 实例方法,第一个参数必须是self
        # self 引用实例本身
        print(f"实例属性1: {self.instance_attribute1}")
        print(f"传入参数: {other_param}")
        return self.instance_attribute1 + other_param

    @classmethod
    def class_method(cls, data): # 类方法,第一个参数是类本身(约定为cls)
        print(f"这是一个类方法,访问类属性: {cls.class_attribute}")
        # cls 可以用来创建类的实例,例如工厂方法
        return cls(data, "default_value_from_classmethod")

    @staticmethod
    def static_method(x, y): # 静态方法,不隐式传递self或cls
        print("这是一个静态方法,它不依赖实例或类状态。")
        return x + y
1.2 对比Java类定义
  • Java: public class MyClassName extends BaseClass implements Interface1, Interface2 { ... }
  • 主要区别: Python原生支持从多个具体类继承(多重继承),而Java类是单继承,但可以通过实现多个接口来达到类似效果。

二、初始化方法 (__init__) 与Java构造器

<a name="2-初始化方法---init----vs-java-constructor"></a>

  • Python: __init__(self, ...) 是一个特殊的初始化方法。当一个对象被创建后(对象本身通常由 __new__ 方法创建,但开发者较少直接重写 __new__),__init__ 会被自动调用,用于设置实例的初始状态和属性。self 参数是对新创建实例的引用。
  • Java: 构造方法名与类名相同,没有返回类型。this 关键字在非静态方法中隐式可用,指向当前实例。

三、self 关键字 与 Java的 this

<a name="3-self--vs--this"></a>

  • Python: self 必须作为实例方法的第一个参数显式声明。它代表实例对象本身。当调用实例方法如 instance.method(arg) 时,Python解释器会自动将 instance 传递给 self 参数。
  • Java: this 是一个关键字,在非静态方法中隐式指向当前实例,无需在参数列表中声明。

四、属性与方法详解

<a name="4-属性与方法-attributes-and-methods"></a>

4.1 实例属性与类属性
  • 实例属性: 通常在 __init__ 方法中通过 self.attribute_name = value 定义,它们属于每个独立的实例。
  • 类属性: 直接在类体中、方法之外定义的属性。它们由该类的所有实例共享。可以通过类名 (ClassName.class_attribute) 或实例 (instance.class_attribute,如果实例没有同名的实例属性来覆盖它) 访问。
4.2 实例方法、类方法 (@classmethod) 与静态方法 (@staticmethod)
  • 实例方法: 定义在类中的函数,第一个参数是 self,用于操作实例的状态和行为。
  • 类方法 (@classmethod): 使用 @classmethod 装饰器定义,其第一个参数是类本身 (约定命名为 cls)。类方法可以访问和修改类属性,常用于实现工厂方法(返回类的实例)或操作与类整体相关的状态。
  • 静态方法 (@staticmethod): 使用 @staticmethod 装饰器定义,它不接收隐式的 selfcls 参数。静态方法在逻辑上属于这个类,但其行为不依赖于实例状态或类状态,更像是一个定义在类命名空间内的普通工具函数 (类似于Java中的 static 工具方法)。

五、封装与访问控制:Python的约定与Java的强制

<a name="5-封装与访问控制-encapsulation--access-control"></a>

Python在封装和访问控制方面与Java有显著不同。

5.1 Python的访问控制约定

Python 没有严格的 private, protected, public 关键字来强制访问控制。其封装更多依赖于“约定”和名称修饰。

  • 单下划线前缀 (_protected_member): 按照约定,这被视为“受保护的”成员。它暗示这是类的内部实现细节,不建议从类外部直接访问。但这仅仅是一个提示,Python解释器并不会阻止外部访问(体现了“我们都是成年人”的哲学)。
  • vs. Java: Java通过 public, protected, default (包可见性), private 关键字来强制实现不同级别的访问控制。
5.2 名称修饰 (__private_member)
  • 双下划线前缀 (__private_member) (不以双下划线结尾): 当在类定义中使用时,会触发Python的名称修饰 (name mangling) 机制。解释器会自动将这样的名称改写为 _ClassName__private_member 的形式。
  • 目的: 主要目的是为了避免子类意外覆盖父类的“私有”成员,从而在一定程度上实现了“伪私有”。但它并非真正的私有,因为仍然可以通过改写后的名称 (_ClassName__private_member) 访问到。
class MySecretiveClass:
    def __init__(self):
        self.public_data = "Public"
        self._protected_data = "Protected"
        self.__private_data = "Private"

    def get_private_data_internally(self):
        return self.__private_data

obj = MySecretiveClass()
print(obj.public_data)
print(obj._protected_data) # 可以访问,但不推荐
# print(obj.__private_data) # 会报 AttributeError
print(obj._MySecretiveClass__private_data) # 可以通过名称修饰后的名字访问
print(obj.get_private_data_internally())

六、继承机制:Python的多重继承与MRO

<a name="6-继承-inheritance"></a>

6.1 Python的继承语法与多重继承

Python通过在类定义时括号内指定父类来实现继承:class DerivedClass(BaseClass1, BaseClass2, ...): ...。

Python支持多重继承,即一个子类可以同时从多个父类继承属性和方法。

6.2 方法解析顺序 (MRO - Method Resolution Order)

在多重继承中,当调用一个方法时,Python使用 C3 线性化算法来确定应该在哪一个父类中查找该方法。MRO定义了类及其父类的查找顺序。可以通过 ClassName.mro()ClassName.__mro__ 查看一个类的MRO。

6.3 super() 函数的应用

super() 函数用于在子类中调用父类的方法。它返回一个特殊的代理对象,该对象会按照MRO去查找并调用合适的父类方法。

  • super().__init__(...):常用于在子类的 __init__ 中调用父类的初始化方法。
  • super().method_name(...):调用父类的同名方法。 在Python 3中,super() 可以无参数调用 (在实例方法中等价于 super(CurrentClass, self))。
class ParentA:
    def __init__(self, a):
        print("ParentA init")
        self.a = a
    def greet(self):
        print("Hello from ParentA")

class ParentB:
    def __init__(self, b):
        print("ParentB init")
        self.b = b
    def greet(self): # ParentB也有greet方法
        print("Greetings from ParentB")

class Child(ParentA, ParentB): # 多重继承
    def __init__(self, a, b, c):
        print("Child init")
        # super().__init__(a) #  会根据MRO调用ParentA的__init__
        ParentA.__init__(self, a) # 显式调用ParentA的__init__
        ParentB.__init__(self, b) # 显式调用ParentB的__init__
        self.c = c

    def child_greet(self):
        super().greet() # 会根据MRO调用ParentA的greet
        ParentB.greet(self) # 显式调用ParentB的greet

# print(Child.mro())
my_child = Child('val_a', 'val_b', 'val_c')
my_child.child_greet()
  • vs. Java: Java类是单继承 (class DerivedClass extends BaseClass { ... })。Java使用 super.method()super(...) 调用父类的方法或构造器。

七、多态与Python的鸭子类型

<a name="7-多态与鸭子类型-polymorphism--duck-typing"></a>

Python的多态更多地体现在鸭子类型 (Duck Typing) 上:“如果一个东西走起来像鸭子,叫起来像鸭子,那么它就是一只鸭子。”

这意味着Python不关注对象的显式类型是什么,而是关注对象是否具有期望的方法和属性(即行为)。只要不同的对象实现了同样接口(同样的方法名和参数签名),它们就可以在代码中被同等对待和互换使用,无需强制它们继承自同一个基类或实现同一个显式接口。

  • vs. Java: Java的多态通常基于显式的继承(子类重写父类方法)和接口实现。类型兼容性通常在编译时检查。Python的鸭子类型更为灵活,检查主要发生在运行时。

八、常用魔法方法 (Dunder Methods)

<a name="8-常用魔法方法-dunder-methods--special-methods"></a>

魔法方法(Dunder methods,来自Double Underscore)是以双下划线开头和结尾的方法 (如 __init__, __str__)。它们在Python中有特殊的含义,会在特定情况下被Python解释器自动调用,用于实现特定的对象行为或协议。

8.1 __init__, __str__, __repr__
  • __init__(self, ...): 对象初始化方法,已讨论。
  • __str__(self): 当对对象使用 print() 函数或 str() 内置函数时调用。必须返回一个字符串,作为对象的“非正式”或用户友好的字符串表示。类似于Java的 Object.toString()
  • __repr__(self): 当对对象使用 repr() 内置函数时调用(交互式解释器中直接输入变量名回车也会触发)。应返回一个“官方的”字符串表示,理想情况下这个字符串是一个有效的Python表达式,可以用来重新创建具有相同值的对象。如果做不到,通常返回一个形如 <...some useful description...> 的字符串。
    • 约定: 若定义了 __str__ 但未定义 __repr__repr() 通常会退用 __str__。最佳实践是两者都定义,或至少定义一个有用的 __repr__ (若只定义 __repr__str() 也会用它)。
8.2 容器与集合类魔法方法 (__len__, __getitem__等)
  • __len__(self): len(obj) 时调用,返回对象“长度”。
  • __getitem__(self, key): obj[key] (索引或键访问) 时调用。
  • __setitem__(self, key, value): obj[key] = value 时调用。
  • __delitem__(self, key): del obj[key] 时调用。
  • __iter__(self): 对对象迭代时 (如 for 循环) 首先调用,应返回一个迭代器对象。
  • __next__(self): 由迭代器对象实现,迭代每一步调用,返回下一元素。无更多元素时抛 StopIteration
8.3 可调用对象 __call__
  • __call__(self, *args, **kwargs): 如果类的实例实现了 __call__ 方法,那么这个实例就可以像函数一样被“调用”:instance_obj(arg1, arg2)
8.4 比较操作符 (__eq__, __lt__等)
  • __eq__(self, other) (等于 ==)
  • __ne__(self, other) (不等于 !=)
  • __lt__(self, other) (小于 <)
  • __le__(self, other) (小于等于 <=)
  • __gt__(self, other) (大于 >)
  • __ge__(self, other) (大于等于 >=) 实现这些方法可以让自定义对象支持比较运算。
8.5 析构器 __del__ (注意事项)
  • __del__(self): 理论上在对象即将被垃圾回收器销毁前调用。
  • 警告: 不应依赖 __del__ 来执行关键的资源清理操作 (如关闭文件、数据库连接、释放锁等)。其调用时机不可靠,也不保证一定会被调用。关键清理应使用 try...finallywith 语句(上下文管理器)。
  • vs. Java: 不完全等同于Java的 finalize() 方法,后者也有类似不可靠性和不推荐使用的问题。

九、对象的属性字典 (__dict__) 与 __slots__

&lt;a name="9-对象的属性字典---dict----">&lt;/a>

大多数Python的自定义对象实例都有一个名为 __dict__ 的特殊属性。

  • __dict__: 是一个字典,存储了该实例的所有可写属性及其对应的值。 Python

    class MyClass:
        class_var = "I am a class variable"
        def __init__(self, x, y):
            self.x = x # 实例变量
            self.y = y # 实例变量
    
    obj = MyClass(10, 20)
    print(obj.__dict__)  # 输出 (通常): {'x': 10, 'y': 20}
    obj.z = 30 # 动态添加实例变量
    print(obj.__dict__)  # 输出 (通常): {'x': 10, 'y': 20, 'z': 30}
    
    print(MyClass.__dict__) # 显示类属性、方法等
    
  • __slots__: 如果类定义中使用了 __slots__ 特殊属性(一个字符串元组,列出允许的实例属性名),那么该类的实例默认将不再拥有 __dict__ 属性(除非 __dict__ 本身也被列在 __slots__ 中)。这样做可以节省内存,但也意味着不能再给这些实例动态添加 __slots__ 未声明的属性。

I will proceed with the next section: VI. 模块 (Modules) 与 包 (Packages) in the next response.