在Python开发中,getter(获取器)和setter(设置器)是控制类属性访问的核心技巧,但很多初学者容易陷入两个误区:一是混淆"单下划线属性的访问权限",二是误以为getter/setter是"多余操作"。今天就用最规范的代码示例,带你吃透Python getter/setter的正确用法,避开常见坑点。
先澄清一个关键认知(必看避坑)
很多人误以为"单下划线开头的属性(_attr)是私有属性,外部不能访问",这是错误的!
Python中属性的访问权限约定(无真正"强制私有",靠约定约束):
-
单下划线 _attr :受保护属性(protected),外部完全可以访问、修改,只是开发者之间的约定------"这是类的内部属性,外部尽量不要直接操作"。
-
双下划线 __attr:私有属性(private),Python会自动进行"名称改写",外部无法直接通过__attr访问(但仍可通过特殊方式间接访问,本质是一种保护机制,而非绝对禁止)。
而我们使用getter/setter的核心目的,不是为了"禁止访问",而是为了:控制属性的读写逻辑、做数据校验、避免外部直接操作内部属性导致的异常,同时保持外部调用的简洁性。
Python getter/setter 标准实现(@property 装饰器)
Python不推荐像Java那样写getXxx()、setXxx()方法,而是用内置的@property装饰器实现getter和setter,对外表现为"普通属性",对内实现"可控逻辑",优雅又简洁。
下面用3个递进式示例,从基础到进阶,覆盖90%的实际使用场景。
示例1:基础版(只读属性,只有getter)
当属性的值是"计算得出",或者不希望外部修改时,只定义getter,不定义setter,实现"只读"效果。
python
class Circle:
def __init__(self, radius):
# 单下划线:受保护属性,约定外部不直接操作
self._radius = radius
# 定义getter:用@property装饰器,方法名就是对外暴露的属性名
@property
def radius(self):
"""获取圆的半径(getter)"""
return self._radius
# 定义getter:计算圆的面积(只读,无setter)
@property
def area(self):
"""获取圆的面积(只读属性,无法修改)"""
return 3.14 * self._radius ** 2
# 测试使用
if __name__ == "__main__":
circle = Circle(5)
# 访问getter:像普通属性一样,无需加括号
print(f"圆的半径:{circle.radius}") # 输出:圆的半径:5
print(f"圆的面积:{circle.area}") # 输出:圆的面积:78.5
# 尝试修改只读属性(无setter),会报错
# circle.area = 100 # AttributeError: can't set attribute
# 虽然可以直接修改受保护属性(语法允许),但违反约定,不推荐
# circle._radius = 10 # 不推荐的操作
核心要点:只有@property装饰的方法,就是getter;无对应的setter时,属性为只读,外部无法赋值。
示例2:进阶版(可读可写,带数据校验)
这是最常用的场景:允许外部修改属性,但需要对修改的值做校验(比如类型、范围限制),避免传入无效数据。此时需要搭配@属性名.setter装饰器实现setter。
python
class Student:
def __init__(self, name, age):
# 单下划线受保护属性,内部存储真实值
self._name = name
self._age = age
# ------------------------------
# name的getter和setter
# ------------------------------
@property
def name(self):
"""获取学生姓名(getter)"""
return self._name
@name.setter # 注意:装饰器名称必须和getter方法名一致
def name(self, value):
"""设置学生姓名(setter),带校验"""
# 校验1:姓名必须是字符串
if not isinstance(value, str):
raise TypeError("姓名必须是字符串类型")
# 校验2:姓名不能为空,且长度在2-10之间
if len(value.strip()) not in range(2, 11):
raise ValueError("姓名长度必须在2-10个字符之间")
# 校验通过,才赋值给内部属性
self._name = value.strip()
# ------------------------------
# age的getter和setter
# ------------------------------
@property
def age(self):
"""获取学生年龄(getter)"""
return self._age
@age.setter
def age(self, value):
"""设置学生年龄(setter),带校验"""
# 校验:年龄必须是0-150之间的整数
if not isinstance(value, int):
raise TypeError("年龄必须是整数类型")
if value < 0 or value > 150:
raise ValueError("年龄必须在0-150之间")
self._age = value
# 测试使用
if __name__ == "__main__":
student = Student("张三", 20)
# 访问getter
print(f"姓名:{student.name},年龄:{student.age}") # 输出:姓名:张三,年龄:20
# 修改属性(自动调用setter,触发校验)
student.name = "李四" # 校验通过,正常赋值
student.age = 22 # 校验通过,正常赋值
print(f"修改后:姓名:{student.name},年龄:{student.age}") # 输出:修改后:姓名:李四,年龄:22
# 测试无效值(触发校验报错)
# student.name = 123 # TypeError: 姓名必须是字符串类型
# student.name = "李" # ValueError: 姓名长度必须在2-10个字符之间
# student.age = 200 # ValueError: 年龄必须在0-150之间
核心要点:setter的装饰器名称必须和getter方法名完全一致(如@name.setter对应@property def name());setter中可以加入任意校验逻辑,确保数据合法性。
示例3:完整版(可读可写可删除,搭配deleter)
如果需要允许外部"删除"属性,可以搭配@属性名.deleter装饰器,实现属性的删除逻辑(通常用于重置属性值)。
python
class User:
def __init__(self, username, email):
self._username = username
self._email = email
# getter:获取用户名
@property
def username(self):
return self._username
# setter:设置用户名(带校验)
@username.setter
def username(self, value):
if not value.isalnum():
raise ValueError("用户名只能包含字母和数字")
self._username = value
# deleter:删除用户名(重置为默认值)
@username.deleter
def username(self):
self._username = "default_user" # 不是真正删除,而是重置
print("用户名已重置为默认值")
# email的getter和setter(简化版,无复杂校验)
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if "@" not in value:
raise ValueError("邮箱格式不正确")
self._email = value
# 测试使用
if __name__ == "__main__":
user = User("zhangsan123", "zhangsan@example.com")
print(f"用户名:{user.username},邮箱:{user.email}")
# 修改属性
user.username = "lisi456"
user.email = "lisi@example.com"
print(f"修改后:{user.username},{user.email}")
# 删除属性(触发deleter)
del user.username
print(f"删除后用户名:{user.username}") # 输出:删除后用户名:default_user
# 尝试删除无deleter的属性,会报错
# del user.email # AttributeError: can't delete attribute
核心要点:deleter不是"真正删除属性",而是执行自定义的重置逻辑;只有定义了@属性名.deleter,才能用del语句操作该属性。
关键总结(吃透核心)
-
getter/setter 的本质:不是为了"隐藏属性",而是为了"控制属性的读写逻辑",避免无效数据,提升代码健壮性。
-
装饰器用法 :
@property对应getter,@属性名.setter对应setter,@属性名.deleter对应deleter,三者名称必须一致。 -
属性访问约定:单下划线 _attr 是受保护属性,外部可访问但不推荐直接操作;双下划线 __attr 是私有属性,外部无法直接访问(名称改写)。
-
外部调用方式:无论getter/setter/deleter如何定义,外部都像操作普通属性一样(obj.属性 访问、obj.属性=值 修改、del obj.属性 删除),简洁优雅。
常见误区避坑
-
误区1:认为"单下划线属性不能访问" → 错误,单下划线只是约定,外部可正常访问,只是不推荐直接修改。
-
误区2:不用@property,直接写get_name()、set_name()方法 → 不推荐,违背Python"优雅简洁"的设计哲学,外部调用繁琐。
-
误区3:过度使用双下划线 __attr → 没必要,大多数场景下,单下划线+getter/setter足以满足需求,双下划线会增加代码复杂度。