Python 类、继承与多态机制深度解析

Nora
Nora
发布时间:2026-10-13| 3人浏览过
原创

本文深入解析 Python 面向对象编程核心机制。从类的定义与 __init__ 构造函数入手,详解特殊方法如 __repr__、__str__ 及运算符重载 __add__ 的应用。探讨继承中的方法重写与动态绑定特性,分析 Python 缺乏严格访问控制与静态类型检查对多态实现的影响,并介绍通过约定模拟接口及 super 函数调用父类方法的最佳实践。

类的定义

若需定义一个表示二维坐标点的类 Point,具体实现如下:

# point.py
class Point:
  def __init__(self, x=0, y=0):
    self.x, self.y = x, y

最基础的是 __init__ 方法,其功能类似于 C++ 或 Java 中的构造函数。所有以双下划线 __ 开头和结尾的方法均属于特殊方法,除了 __init__ 外还有诸多其他特殊方法,后续章节将逐一介绍。

参数 self 的作用类似于 C++ 中的 this,用于指向当前实例。所有实例方法均包含此参数,但在调用时无需显式指定该参数。

>>> from point import *
>>> p = Point(10, 10) # __init__ 被调用
>>> type(p)
<class 'point.Point'>
>>> p.x, p.y
(10, 10)

绝大多数特殊方法(含 __init__)均为隐式调用,开发者不应直接调用它们。

在 Python “一切皆对象”的理念下,类本身也是一个对象:

>>> type(Point)
<class 'type'>
>>> dir(Point)
['__class__', '__delattr__', '__dict__', ..., '__init__', ...]
>>> Point.__class__
<class 'type'>

Point 是 type 类的实例,这与 p 是 Point 类的实例在本质上是相同的。

现为类添加一个 set 方法:

class Point:
  ...
  def set(self, x, y):
    self.x, self.y = x, y

>>> p = Point(10, 10)
>>> p.set(0, 0)
>>> p.x, p.y
(0, 0)

调用 p.set(...) 实际上是一种语法糖,等价于写成 Point.set(p, ...),这种写法能更直观地看出 p 即对应 self 参数:

>>> Point.set(p, 0, 0)
>>> p.x, p.y
(0, 0)

值得注意的是,self 并非 Python 关键字,甚至可以使用其他名称替代,例如 this:

class Point:
  ...
  def set(this, x, y):
    this.x, this.y = x, y

与 C++ 不同,Python 中“成员变量”必须添加 self. 前缀,否则该变量将变为类的属性(类似于 C++ 的静态成员),而非对象的属性。

访问控制

Python 不提供 public、protected 或 private 等访问控制修饰符。若需表示“私有”,惯例是添加双下划线前缀。

class Point:
  def __init__(self, x=0, y=0):
    self.__x, self.__y = x, y

  def set(self, x, y):
    self.__x, self.__y = x, y

  def __f(self):
    pass

__x、__y 和 __f 即被视为私有成员:

>>> p = Point(10, 10)
>>> p.__x
...
AttributeError: 'Point' object has no attribute '__x'
>>> p.__f()
...
AttributeError: 'Point' object has no attribute '__f'

__repr__

尝试打印 Point 实例:

>>> p = Point(10, 10)
>>> p
<point.Point object at 0x000000000272AA20>

通常,这并非我们期望的输出结果,我们更希望看到如下格式:

>>> p
Point(10, 10)

通过添加特殊方法 __repr__ 即可实现该效果:

class Point:
  def __repr__(self):
    return 'Point({}, {})'.format(self.__x, self.__y)

可以看出,在交互模式中打印 p 时,实际上调用了 repr(p):

>>> repr(p) 'Point(10, 10)'

__str__

若未提供 __str__,str() 函数默认使用 repr() 的结果。 两者均用于表示对象的字符串形式,但存在细微差别。简而言之,repr() 的结果面向解释器,通常需为合法的 Python 代码,例如 Point(10, 10);而 str() 的结果面向用户,力求简洁,例如 (10, 10)。

依据上述原则,我们为 Point 类定义 __str__ 如下:

class Point:
  def __str__(self):
    return '({}, {})'.format(self.__x, self.__y)

__add__

两个坐标点相加是一项合理的需求。

>>> p1 = Point(10, 10)
>>> p2 = Point(10, 10)
>>> p3 = p1 + p2
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'
 

通过添加特殊方法 __add__ 即可实现:

class Point:
  def __add__(self, other):
    return Point(self.__x + other.__x, self.__y + other.__y)

>>> p3 = p1 + p2
>>> p3
Point(20, 20)

这类似于 C++ 中的操作符重载。 Python 的内建类型,如字符串和列表,均已“重载”了 + 操作符。

特殊方法种类繁多,此处不再逐一列举。

继承

举一个教科书式的常见例子。Circle 和 Rectangle 类继承自 Shape 类,不同图形的面积(area)计算方式各不相同。

# shape.py

class Shape:
  def area(self):
    return 0.0
    
class Circle(Shape):
  def __init__(self, r=0.0):
    self.r = r

  def area(self):
    return math.pi * self.r * self.r

class Rectangle(Shape):
  def __init__(self, a, b):
    self.a, self.b = a, b

  def area(self):
    return self.a * self.b

其用法较为直接:

>>> from shape import *
>>> circle = Circle(3.0)
>>> circle.area()
28.274333882308138
>>> rectangle = Rectangle(2.0, 3.0)
>>> rectangle.area()
6.0
 

若 Circle 类未定义自己的 area 方法:

class Circle(Shape):
  pass 
 

那么它将继承父类 Shape 的 area 方法:

>>> Shape.area is Circle.area
True

一旦 Circle 定义了自身的 area 方法,从 Shape 继承而来的 area 方法即被重写(overwrite):

>>> from shape import *
>>> Shape.area is Circle.area
False
 

通过查看类的字典可以更清晰地观察到这一点:

>>> Shape.__dict__['area']
<function Shape.area at 0x0000000001FDB9D8>
>>> Circle.__dict__['area']
<function Circle.area at 0x0000000001FDBB70>
 

因此,子类重写父类方法,实质上是将相同的属性名绑定到不同的函数对象。由此可见,Python 中并不存在传统意义上的覆写(override)概念。

同理,即使 Shape 未定义 area 方法也是可行的,Shape 在此处充当“接口”角色,但无法获得语法层面的保证。

甚至可以动态地添加方法:

class Circle(Shape):
  ...
  # def area(self):
    # return math.pi * self.r * self.r

# 为 Circle 添加 area 方法。
Circle.area = lambda self: math.pi * self.r * self.r
 

动态语言通常具备这种灵活性,Python 亦不例外。

Python 官方教程「9. Classes」开篇即指出:

Compared with other programming languages, Python's class mechanism adds classes with a minimum of new syntax and semantics.

Python 以极少的语法和语义新增实现了类机制,这一点确实令人惊叹,但也常令熟悉 C++ 或 Java 的程序员感到不适。

多态

如前所述,Python 不存在传统意义上的覆写(override)概念。严格而言,Python 并不直接支持“多态”。

为了解决继承结构中接口与实现的问题,或为了更好地践行 Python 面向接口编程(设计模式所倡导的理念),我们需要人为设定一些规范。

请思考 Shape.area() 除了简单返回 0.0 外,是否有更优的实现方式?

以内建模块 asyncio 为例,AbstractEventLoop 原则上是一个接口,类似于 Java 中的接口或 C++ 中的纯虚类,但 Python 并无语法强制保证这一点。为尽量体现 AbstractEventLoop 的接口属性,首先在命名上标识其为抽象(Abstract),然后让每个方法均抛出异常 NotImplementedError。

class AbstractEventLoop:
  def run_forever(self):
    raise NotImplementedError
  ...
 

尽管如此,你仍无法禁止用户实例化 AbstractEventLoop:

loop = asyncio.AbstractEventLoop()
try:
  loop.run_forever()
except NotImplementedError:
  pass
 

C++ 可通过纯虚函数或将构造函数设为 protected 来避免接口被实例化,Java 则提供了完整的语法支持。

你也无法强制子类必须实现“接口”中定义的每一个方法,C++ 的纯虚函数可强制这一点(Java 更不必说)。

即使子类“自以为”实现了“接口”中的方法,也无法保证方法名拼写无误,C++ 的 override 关键字可确保这一点(Java 更不必说)。

静态类型的缺失,使得 Python 难以实现如 C++ 或 Java 那般严格的多态检查机制。因此,面向接口编程在 Python 中更多依赖于程序员的素养。

回到 Shape 的例子,仿照 asyncio 的做法,我们将“接口”修改如下:

class AbstractShape:
  def area(self):
    raise NotImplementedError
 

这样,它才更像一个接口。

super

有时,需要在子类中调用父类的方法。

例如,图形均具有颜色属性,因此不妨在 __init__ 中添加 color 参数:

class AbstractShape:
  def __init__(self, color):
    self.color = color
 

那么子类的 __init__() 方法也需相应调整:

class Circle(AbstractShape):
  def __init__(self, color, r=0.0):
    super().__init__(color)
    self.r = r
 

通过 super 将 color 传递给父类的 __init__()。其实不使用 super 也是可行的:

class Circle(AbstractShape):
  def __init__(self, color, r=0.0):
    AbstractShape.__init__(self, color)
    self.r = r
 

但 super 是推荐的做法,因为它避免了硬编码,并能处理多继承的情况。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。