Q:自定义的类是否可变?
A:这取决于类的设计,但是从技术上讲,Python中的类都是可变的
以下为两个例子
class MyMutableClass:
def __init__(self, value):
self.value = value
def set_value(self, new_value):
self.value = new_value # 允许修改实例状态,因此是可变的
class MyImmutableClass:
def __init__(self, value):
self._value = value # 使用单下划线前缀表示这一个“受保护”的属性,但Python中的受保护只是约定
# 没有提供修改_value的方法,因此可以认为这个类是不可变的(尽管技术上可以通过其他方式修改)
@property
def value(self):
return self._value
# 使用示例
mutable = MyMutableClass(10)
print(mutable.value) # 10
mutable.set_value(20) # 修改了mutable的状态
print(mutable.value) # 20
immutable = MyImmutableClass(10)
print(immutable.value) # 10
immutable._value= 20 # 这会违反类的封装原则,但技术上可行
print(immutable.value) # 20
示例代码中,尽管用单下划线为前缀表示这是一个“受保护”属性,但这只是一种约定俗成,并没有语法层面的严格限制,类外部依然可以访问到。就如同全大写的变量视作常量一样。
如果用双下划线作为前缀呢?
# 双下划线开头的属性是私有属性,不应该被直接访问
class MyImmutableClass:
def __init__(self, value):
self.__value = value # 使用双下划线前缀表示这一个“私有”的属性
@property
def value(self):
return self.__value
immutable=MyImmutableClass(10)
print(immutable.value) # 10
print(dir(immutable)) # 查看类的属性 没有__value
"""
['_MyImmutableClass__value', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'value']
"""
immutable.__value=20 # 直接修改__value属性,但实际上是创建了一个新的属性
print(immutable.value) # 10 没有修改成功
immutable._MyImmutableClass__value=20 # 触发名称改写,通过这种方式修改私有属性
print(immutable.value) # 20 修改成功
名称改写:在类定义中使用双下划线(__)作为属性名的前缀时,Python解释器会自动将这些属性名改写为一个包含类名前缀的新名称。
改写的规则是,在属性名前加上单下划线和类名。例如代码中的__value
被改写成_MyImmutableClass__vlaue
。
从语法角度,严格限制外部访问类内部“私有”属性的规则并不存在。
类内部单下划线开头的,被约定为“受保护”属性,类外部可以通过
类内部双下划线开头的变量,类外部直接访问会触发“名称改写”,因此无法访问到,但如果知道改写后的名称,则可以正常访问。
参考:
https://docs.python.org/zh-cn/3/tutorial/classes.html#private-variables
https://docs.python.org/zh-cn/3/reference/expressions.html#private-name-mangling