☰
Python类的定义和使用详情
2026/10/10 19:24:39 网站建设 项目流程

前言


写一个能用的类不难,写一个"经得起扩展"的类却需要理解不少语法细节:class语句的完整形式、类文档串(docstring)的位置、构造函数的约束、继承与方法重写、以及双下划线开头的名字为什么会"消失"。


常见误解有两个:一是以为__name这种双下划线前缀的属性是"私有成员",外部绝对访问不到;二是以为重写父类方法时不需要调用super()。前者是误解,Python 只是做名字改写(name mangling),不是访问控制;后者要看场景,__init__的重写几乎一定要调super().__init__(),否则父类的初始化逻辑会被跳过。


本文按"怎么定义、怎么用、怎么继承、怎么封装"的顺序,把类的语法细节讲全。


一、class语句的完整形式


class语句的基本形式是class 类名(基类列表):,类体就是缩进块。类体第一条语句如果是一个字符串字面量,会被当作该类的文档串。


# 适用于 Python 3.8+
class Circle:
"""表示一个圆。

属性:
radius: 半径,必须为正数。
"""

pi = 3.141592653589793 # 类属性

def __init__(self, radius):
if radius <= 0:
raise ValueError("半径必须为正")
self.radius = radius

def area(self):
return Circle.pi * self.radius ** 2


c = Circle(2)
print(c.area()) # 12.566370614359172
print(Circle.__doc__.splitlines()[0]) # 表示一个圆。

几个语法要点:



  • 类名习惯用大驼峰(CapWords),函数用蛇形(snake_case);

  • 基类列表可以为空,此时隐式继承object;

  • 类体里可以写任意语句(赋值、条件、循环、嵌套函数),它们在定义时执行;

  • 类体的文档串必须紧跟在class行之后,否则不会被识别为__doc__。


二、__init__与实例化


__init__不是"构造函数",它只负责初始化已经被创建出来的实例,真正的创建由__new__完成。__init__必须返回None,返回别的值会抛TypeError。


# 适用于 Python 3.8+
class User:
def __init__(self, name, roles=None):
self.name = name
# 不要用可变对象当默认参数
self.roles = list(roles) if roles is not None else []

def add_role(self, role):
if role not in self.roles:
self.roles.append(role)

def __repr__(self):
return f"User({self.name!r}, roles={self.roles!r})"


u = User("Ada")
u.add_role("admin")
print(u) # User('Ada', roles=['admin'])
print(User("Bob").roles) # []

这里刻意避开了def __init__(self, roles=[])这个经典陷阱:默认参数在函数定义时求值一次,可变默认参数会被所有调用共享。


三、继承、重写与super()


子类用class Sub(Base):声明。重写方法时,如果希望保留父类行为,用super()调用父类实现。super()走的是方法解析顺序(MRO),不是简单的"父类"。


# 适用于 Python 3.8+
class Animal:
def __init__(self, name):
self.name = name

def speak(self):
return "..."


class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 别忘了这一行
self.breed = breed

def speak(self):
return f"{self.name} 说:汪汪"


d = Dog("Rex", "柴犬")
print(d.speak()) # Rex 说:汪汪
print(isinstance(d, Animal)) # True
print(Dog.__mro__) # (Dog, Animal, object)

如果子类__init__忘了super().__init__(name),self.name就不会被设置,之后调用speak()会直接AttributeError。




关系判断方式结果



子类实例是父类实例吗isinstance(d, Animal)True

子类是父类的子类吗issubclass(Dog, Animal)True

子类继承父类私有名吗名字改写后仍可访问但名字被改写

子类能覆盖类属性吗在子类里重新赋值即可可以



四、名字改写不是私有


以双下划线开头、且最多一个下划线结尾的名字(例如__secret)会被"改写"成_类名__secret。这叫做名字改写(name mangling),目的是避免子类意外覆盖,它不是访问控制。


# 适用于 Python 3.8+
class Vault:
def __init__(self):
self.__secret = 42
self._single = "ok" # 单下划线只是约定:内部使用
self.__dunder__ = "特殊方法名,不改写"

def reveal(self):
return self.__secret


v = Vault()
print(v.reveal()) # 42
print(v._Vault__secret) # 42,改写后照样能访问
print(vars(v))
# {'_Vault__secret': 42, '_single': 'ok', '__dunder__': '特殊方法名,不改写'}

结论:双下划线前缀只能防止"子类不小心重名",阻止不了任何人访问。真正的封装靠的是清晰的接口约定,而不是名字前缀。


五、用抽象基类定义接口


如果一个类只用来规定"子类必须实现哪些方法",可以用abc模块的ABC和@abstractmethod。含未实现抽象方法的类不能被实例化。


# 适用于 Python 3.8+
from abc import ABC, abstractmethod


class Shape(ABC):
@abstractmethod
def area(self):
"""子类必须实现。"""

def describe(self):
return f"面积是 {self.area()}"


class Square(Shape):
def __init__(self, side):
self.side = side

def area(self):
return self.side ** 2


print(Square(3).describe()) # 面积是 9
try:
Shape() # TypeError: Can't instantiate abstract class
except TypeError as e:
print("TypeError:", e)

六、综合示例


# 适用于 Python 3.8+
class Stack:
"""一个简单的后进先出栈。"""

def __init__(self, items=None):
self._items = list(items) if items is not None else []

def push(self, item):
self._items.append(item)

def pop(self):
if not self._items:
raise IndexError("pop from empty stack")
return self._items.pop()

def __len__(self):
return len(self._items)

def __repr__(self):
return f"Stack({self._items!r})"


s = Stack([1, 2])
s.push(3)
print(len(s)) # 3
print(s.pop()) # 3
print(s) # Stack([1, 2])

常见坑点



  1. 可变默认参数


❌


``python # 适用于 Python 3.8+ class A: def __init__(self, items=[]): self.items = items``


✅


``python # 适用于 Python 3.8+ class A: def __init__(self, items=None): self.items = [] if items is None else list(items)``



  1. 子类__init__不调super().__init__()


❌


``python # 适用于 Python 3.8+ class Dog(Animal): def __init__(self, name, breed): self.breed = breed # 缺 name``


✅


``python # 适用于 Python 3.8+ class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed``



  1. 文档串位置放错


❌


``python # 适用于 Python 3.8+ class A: pi = 3.14 "这是圆的说明" # 不是文档串``


✅


``python # 适用于 Python 3.8+ class A: "这是圆的说明" pi = 3.14``



  1. 以为双下划线是真私有


❌


``python # 适用于 Python 3.8+ class A: def __init__(self): self.__v = 1 # 以为外部 a.__v 一定失败就一定安全``


✅


```python # 适用于 Python 3.8+ # 用单下划线表达"内部使用",并提供公开方法访问 class A: def __init__(self): self._v = 1


def get_v(self): return self._v ```



  1. 重写方法时签名不一致


❌


```python # 适用于 Python 3.8+ class Base: def save(self, path, overwrite=False): ...


class Sub(Base): def save(self, path): # 少了一个参数,替换使用时会崩 ... ```


✅


``python # 适用于 Python 3.8+ class Sub(Base): def save(self, path, overwrite=False): ...``



  1. 抽象基类忘了实现抽象方法就想实例化


❌


```python # 适用于 Python 3.8+ class Square(Shape): pass


Square() # TypeError ```


✅


``python # 适用于 Python 3.8+ class Square(Shape): def area(self): return 1``



  1. 类属性与实例属性同名造成困惑


❌


```python # 适用于 Python 3.8+ class A: count = 0


def bump(self): self.count += 1 # 生成实例属性,类属性不变 ```


✅


```python # 适用于 Python 3.8+ class A: count = 0


def bump(self): type(self).count += 1 ```


总结




语法点要点



class语句类体在定义时执行一次

文档串必须是类体第一条语句

__init__只初始化,必须返回None

继承super().__init__()不要漏

名字改写__x变_类名__x,非私有

ABC未实现抽象方法不能实例化



定义类时,最容易出错的不是语法,而是几个约定:可变默认参数、super().__init__()、以及把双下划线当私有。把接口用文档串和公开方法表达清楚,比堆名字前缀更可靠。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询