核心概念回顾
我们快速回顾一下 property 的工作原理。property 是一个内置的装饰器,它将一个类的方法转换成属性的“访问器”,当你定义一个 @property 时,你实际上是在创建一个描述符,它拦截了对属性的访问(obj.attr)、赋值(obj.attr = value)和删除(del obj.attr)操作。

一个完整的 property 通常由三个部分组成:
- getter (
@property): 当你读取属性时被调用。 - setter (
@attr.setter): 当你给属性赋值时被调用。 - deleter (
@attr.deleter): 当你删除属性时被调用。
继承 property 的几种情况
当子类继承父类的 property 时,主要会分为以下几种情况:
- 直接继承
property(不进行重写) - 重写
getter方法 - 重写
setter方法 - 重写
getter和setter方法 - 在子类中创建全新的
property
让我们通过一个具体的例子来逐一分析。
父类定义
我们定义一个父类 Person,它有一个 age 属性,这个属性通过 property 进行了封装,确保年龄不能为负数。

class Person:
def __init__(self, name, age):
self.name = name
# 注意:这里我们直接调用 setter 方法来初始化 age
# 这样可以复用 setter 中的逻辑,避免代码重复
self.age = age
@property
def age(self):
"""Getter for age"""
print("Parent: Getting age...")
return self._age # 使用 _age 作为实际存储的内部变量
@age.setter
def age(self, value):
"""Setter for age"""
print("Parent: Setting age...")
if not isinstance(value, int) or value < 0:
raise ValueError("Age must be a positive integer")
self._age = value
@age.deleter
def age(self):
"""Deleter for age"""
print("Parent: Deleting age...")
del self._age
def __str__(self):
return f"Person(name={self.name}, age={self.age})"
# --- 测试父类 ---
print("--- Testing Parent Class ---")
p = Person("Alice", 30)
print(p) # 读取 age,会调用 getter
p.age = 31 # 设置 age,会调用 setter
print(p.age) # 再次读取 age
try:
p.age = -5 # 尝试设置非法值,会触发 setter 中的检查
except ValueError as e:
print(f"Error: {e}")
del p.age # 删除 age,会调用 deleter
# print(p.age) # 再次读取会报错,因为 _age 已被删除
直接继承 property(不进行重写)
如果子类不重写父类的 property 方法,那么子类会完全继承父类的 getter、setter 和 deleter 逻辑。
class Employee(Person):
def __init__(self, name, age, employee_id):
super().__init__(name, age)
self.employee_id = employee_id
# --- 测试直接继承 ---
print("\n--- Testing Direct Inheritance ---")
e = Employee("Bob", 40, "E12345")
print(e) # 调用父类的 getter
e.age = 41 # 调用父类的 setter
print(e.age)
输出:
--- Testing Direct Inheritance ---
Parent: Setting age...
Parent: Getting age...
Person(name=Bob, age=40)
Parent: Setting age...
Parent: Getting age...
41
可以看到,Employee 类的实例 e 完全使用了 Person 类中定义的 age 属性的所有逻辑。
重写 getter 方法
子类可以只重写 getter 方法,而保留父类的 setter 和 deleter,这在你想改变属性的读取行为,但保留其设置和删除逻辑时非常有用。

class EmployeeWithCustomGetter(Person):
def __init__(self, name, age, employee_id):
super().__init__(name, age)
self.employee_id = employee_id
@property
def age(self):
"""子类重写 getter,返回一个格式化的字符串"""
print("Child: Getting age with custom format...")
# 调用父类的 getter 来获取原始值
parent_age = super().age
return f"{parent_age} years old"
# --- 测试重写 getter ---
print("\n--- Testing Overridden Getter ---")
e_getter = EmployeeWithCustomGetter("Charlie", 35, "E67890")
print(e_getter) # 读取 age,调用子类的 getter
e_getter.age = 36 # 设置 age,仍然调用父类的 setter
print(e_getter.age) # 再次读取,调用子类的 getter
输出:
--- Testing Overridden Getter ---
Parent: Setting age...
Child: Getting age with custom format...
Person(name=Charlie, age=35 years old)
Parent: Setting age...
Child: Getting age with custom format...
36 years old
关键点:
- 当读取
e_getter.age时,EmployeeWithCustomGetter的@property方法被调用。 - 在子类的
getter内部,我们使用super().age来获取父类getter的返回值,这非常重要,因为它可以复用父类的逻辑,或者像这里一样,在父类逻辑的基础上进行扩展。
重写 setter 方法
同样,子类也可以只重写 setter 方法,以改变属性的赋值行为,同时保留父类的 getter。
class EmployeeWithStrictSetter(Person):
def __init__(self, name, age, employee_id):
super().__init__(name, age)
self.employee_id = employee_id
@property
def age(self):
"""我们不重写 getter,所以继承父类的"""
return super().age
@age.setter
def age(self, value):
"""子类重写 setter,增加更严格的检查(例如年龄上限)"""
print("Child: Setting age with stricter rules...")
if value > 65:
raise ValueError("Employee age cannot exceed 65")
# 调用父类的 setter 来复用其基本逻辑(如检查是否为负数)
super().age = value
# --- 测试重写 setter ---
print("\n--- Testing Overridden Setter ---")
e_setter = EmployeeWithStrictSetter("David", 50, "E24680")
print(e_setter)
e_setter.age = 60 # 调用子类的 setter
print(e_setter)
try:
e_setter.age = 70 # 尝试设置超过上限的年龄
except ValueError as e:
print(f"Error: {e}")
输出:
--- Testing Overridden Setter ---
Parent: Setting age...
Person(name=David, age=50)
Child: Setting age with stricter rules...
Parent: Setting age...
Person(name=David, age=60)
Child: Setting age with stricter rules...
Error: Employee age cannot exceed 65
关键点:
- 当设置
e_setter.age = 60时,EmployeeWithStrictSetter的@age.setter方法被调用。 - 我们在子类的
setter中添加了新的检查逻辑(年龄上限)。 - 我们调用
super().age = value,将值传递给父类的setter,这会执行父类中定义的所有原始逻辑(检查是否为负数),并最终设置self._age,这是一种组合和扩展逻辑的绝佳方式。
重写 getter 和 setter 方法
子类可以同时重写 getter 和 setter,完全控制属性的所有行为。
class EmployeeWithFullControl(Person):
# ... (省略 __init__ 和 deleter 继承)
@property
def age(self):
print("Child: Full control - Getting age...")
return super().age
@age.setter
def age(self, value):
print("Child: Full control - Setting age...")
# 可以完全自定义逻辑,甚至不调用 super()
# 但通常推荐调用 super() 来复用父类的健壮性检查
if value > 65:
raise ValueError("Strict age limit in child class!")
super().age = value
# --- 测试重写 getter 和 setter ---
print("\n--- Testing Overridden Getter and Setter ---")
e_full = EmployeeWithFullControl("Eve", 28, "E13579")
print(e_full)
e_full.age = 29
print(e_full)
输出:
--- Testing Overridden Getter and Setter ---
Parent: Setting age...
Child: Full control - Getting age...
Person(name=Eve, age=28)
Child: Full control - Setting age...
Parent: Setting age...
Child: Full control - Getting age...
Person(name=Eve, age=29)
这里的逻辑与前两种情况的组合类似,子类对属性的访问和赋值都拥有了自己的实现。
在子类中创建全新的 property
子类也可以完全忽略父类的 property,使用相同的名字创建一个全新的 property,这相当于在子类中“遮蔽”了父类的属性。
class EmployeeWithNewProperty(Person):
def __init__(self, name, age, employee_id, years_of_service):
super().__init__(name, age)
self.employee_id = employee_id
self.years_of_service = years_of_service
# 全新的 age property,与父类的无关
@property
def age(self):
"""一个全新的 age 属性,代表工龄"""
print("Child: NEW property - Getting years of service as age...")
return self.years_of_service
@age.setter
def age(self, value):
"""设置这个新 age 属性就是设置工龄"""
print("Child: NEW property - Setting years of service...")
if not isinstance(value, int) or value < 0:
raise ValueError("Years of service must be a positive integer")
self.years_of_service = value
# --- 测试创建全新 property ---
print("\n--- Testing Brand New Property ---")
e_new = EmployeeWithNewProperty("Frank", 45, "E98765", 10)
# 注意:父类的 __init__ 会调用父类的 setter 设置 self._age = 45
# 但我们创建了一个全新的 age property
print(e_new) # 这会调用 Person.__str__,而 Person.__str__ 读取的是 self.age
# 子类的 age property 被调用,返回 10 (years_of_service)
# 输出: Person(name=Frank, age=10)
print(f"Employee's new age (service years): {e_new.age}") # 明确读取子类的 age
e_new.age = 11 # 调用子类的 setter
print(f"Updated new age: {e_new.age}")
# 如果我们想访问父类设置的原始年龄怎么办?
# 我们可以访问父类 property 的“存储”变量,但这不是好做法
# 更好的做法是给父类属性起不同的名字,或者在子类中提供方法
print(f"Actual biological age (from parent's _age): {e_new._age}")
输出:
--- Testing Brand New Property ---
Parent: Setting age...
Child: NEW property - Getting years of service as age...
Person(name=Frank, age=10)
Employee's new age (service years): 10
Child: NEW property - Setting years of service...
Updated new age: 11
Actual biological age (from parent's _age): 45
警告:
这种做法(用同名属性遮蔽父类属性)可能会导致非常隐蔽的bug,如上面的例子所示。Person 类的 __str__ 方法期望 self.age 是一个整数,但它却得到了一个代表工龄的整数,这在语义上可能是不正确的。通常情况下,应尽量避免遮蔽父类的属性。
总结与最佳实践
| 场景 | 子类行为 | 关键代码 | 使用场景 |
|---|---|---|---|
| 直接继承 | 完全使用父类的 property 逻辑。 |
无需额外代码。 | 子类是父类的特化,但属性行为不变。 |
| 重写 Getter | 只改变属性的读取方式。 | @propertyreturn super().age |
在读取属性时添加格式化、计算或日志。 |
| 重写 Setter | 只改变属性的赋值方式。 | @attr.settersuper().attr = value |
在赋值前添加更严格的验证、触发其他副作用。 |
| 重写全部 | 完全自定义属性行为。 | 重写 @property 和 @attr.setter |
需要彻底改变属性的工作方式。 |
| 创建新属性 | 用同名属性遮蔽父类属性。 | @property (全新的实现) |
应尽量避免,容易造成混淆和bug。 |
核心原则:
- 优先使用
super():在子类的property方法中,通过super()来调用父类对应的方法,可以复用父类的健壮逻辑,避免代码重复。 - 明确意图:清晰地知道你是想扩展父类的属性行为(推荐),还是想替换它(需谨慎)。
- 避免遮蔽:尽量避免在子类中创建与父类同名的新
property,这会破坏继承的清晰性,如果需要,考虑使用不同的属性名。
通过这种方式,property 的继承使得 Python 的面向对象设计非常灵活和强大,允许你在不修改父类代码的情况下,精确地定制子类的行为。
