Python 私有属性与方法
Python 通过命名约定实现属性的私有化,而非真正的访问控制。
三种命名约定
| 约定 | 示例 | 说明 |
|---|---|---|
| 单下划线 | _var | 内部使用,约定私有 |
| 双下划线 | __var | 名称重整(name mangling) |
| 双下划线前后包围 | __var__ | 魔术方法,系统定义 |
单下划线:约定私有
Python
class Person:
def __init__(self, name):
self._name = name # 约定私有,但仍可访问
def _get_internal(self): # 约定私有方法
return "内部方法"
p = Person("Alice")
print(p._name) # Alice(可访问)
print(p._get_internal()) # 内部方法(可调用)
# 只是约定,不阻止外部访问
单下划线表示"内部使用",模块中
from module import *不导入单下划线开头的对象。
双下划线:名称重整
Python
class Secret:
def __init__(self):
self.__private = "私有数据" # 双下划线触发名称重整
def __private_method(self):
return "私有方法"
s = Secret()
# 直接访问失败
# print(s.__private) # AttributeError
# print(s.__private_method()) # AttributeError
# 通过重整名称访问
print(s._Secret__private) # 私有数据
print(s._Secret__private_method()) # 私有方法
# 查看 __dict__
print(s.__dict__) # {'_Secret__private': '私有数据'}
名称重整规则
Python
__attr 变为 _ClassName__attr
避免子类覆盖
Python
class Base:
def __init__(self):
self.__value = 1
class Derived(Base):
def __init__(self):
super().__init__()
self.__value = 2 # 不覆盖父类的 __value
d = Derived()
print(d._Base__value) # 1(父类保留)
print(d._Derived__value) # 2(子类独立)
私有属性的真正目的
Python
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def withdraw(self, amount):
if amount > 0 and amount <= self.__balance:
self.__balance -= amount
else:
raise ValueError("余额不足")
def get_balance(self):
return self.__balance
acc = BankAccount(1000)
acc.deposit(500)
print(acc.get_balance()) # 1500
# 直接修改"私有"属性绕过验证
acc._BankAccount__balance = -1000 # 可以修改
print(acc.get_balance()) # -1000
Python 私有属性不是真正的访问控制,而是防止子类意外覆盖的机制。
getter 和 setter
使用方法
Python
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
def get_celsius(self):
return self._celsius
def set_celsius(self, value):
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self._celsius = value
def get_fahrenheit(self):
return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.get_celsius()) # 25
t.set_celsius(30)
print(t.get_celsius()) # 30
使用 @property
Python
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9 / 5 + 32
t = Temperature(25)
print(t.celsius) # 25(像属性一样访问)
t.celsius = 30 # 像属性一样赋值,触发 setter
print(t.fahrenheit) # 86.0
私有属性与继承
Python
class Parent:
def __init__(self):
self.__private = "父类私有"
self._protected = "父类保护"
class Child(Parent):
def __init__(self):
super().__init__()
self.__private = "子类私有" # 新的私有属性,不覆盖
def show(self):
print(self._protected) # 可访问
# print(self.__private) # AttributeError
print(self._Child__private) # 子类私有
print(self._Parent__private) # 父类私有
c = Child()
c.show()
# 父类保护
# 子类私有
# 父类私有
魔术方法
Python
class MyClass:
def __init__(self):
self.__private = "私有"
def __str__(self): # 魔术方法
return "MyClass"
obj = MyClass()
print(obj) # MyClass(调用 __str__)
print(obj.__dict__) # {'_MyClass__private': '私有'}
# 魔术方法不会被名称重整
print(obj.__str__) # <method-wrapper '__str' of MyClass object>
__xxx__是魔术方法命名空间,不会触发名称重整,用于 Python 特殊方法。
检查私有属性
Python
class PrivateChecker:
def __init__(self):
self._internal = "内部"
self.__private = "私有"
def list_attrs(self):
print("实例属性:", self.__dict__)
p = PrivateChecker()
p.list_attrs()
# 实例属性: {'_internal': '内部', '_PrivateChecker__private': '私有'}
最佳实践
text
class GoodPractice:
def __init__(self):
# 公开属性:直接使用
self.public = "公开"
# 保护属性:单下划线,子类可访问
self._protected = "保护"
# 私有属性:双下划线,防止子类覆盖
self.__private = "私有"
def public_method(self):
"公开方法"
return self.__private # 内部访问私有属性
def _protected_method(self):
"保护方法"
return "保护方法"
def __private_method(self):
"私有方法"
return "私有方法"
三种命名约定对比
| 约定 | 真正私有 | 用途 |
|---|---|---|
_var | 否 | 内部使用,约定私有 |
__var | 否 | 名称重整,防止子类覆盖 |
__var__ | 否 | 魔术方法,系统定义 |
要点总结
| 要点 | 说明 |
|---|---|
_xxx | 约定私有,仍可访问 |
__xxx | 名称重整为 _Class__xxx |
__xxx__ | 魔术方法,不重整 |
| 无真正私有 | Python 不强制访问控制 |
@property | 推荐的属性访问方式 |
Python 私有属性只是命名约定,本质都可访问;双下划线主要用于防止子类意外覆盖。
D:\git2\jwdev\articles\PYTHON\进阶\面向对象编程\私有属性与方法.md
📝 发现内容有误?点击此处直接编辑