流畅的 Python 阅读记录七:成员属性
Python 中的成员变量(也有说法叫属性)和 C++ 有点不同, 下面是对 Python 的属性 的一些记录.
类属性和成员属性
Python 中的类也是有属性的, 看如下代码.
class Student(object):
name = 'student'
def __init__(self, age):
self.age = age
这里的
name
就是类属性, 而 age
则是成员属性, 这个只能通过某个具体实例访问, 而类属性却可以所有实例都可以访问.allen = Student(10)
print(Student.name) # 类属性可以通过类直接访问
print(allen.name) # 类属性还可以通过实例进行访问
# print(Student.age) # 成员属性不可以通过类直接访问!
print(allen.age) # 成员属性可以通过实例进行访问
类属性和成员属性相同名字
上个例子中类属性是 name
, 成员属性是 age
, 名字不一样, 但是如果它们名字是一样呢?
class Student(object):
name = 'student'
def __init__(self, name):
self.name = name
allen = Student('allen')
'''
>>> print(Student.name) # 类属性很正常
Student
>>> print(allen.name) # 对于这个实例来说, 优先选择成员属性!
allen
>>> del allen.name # 如果删除实例属性
>>> print(allen.name) # 找不到实例属性, 那么就会显示类属性
Student
'''
如何防止成员属性被修改
有的时候不想要让某些成员属性被修改, 在 C++ 中可以设置 private 变量, 但是在 Python 中如何做?
单下划线
有一种有些人使用的约定( 不是官方规范 ), 是变量名前面加一个下划线, 表示这个变量是只读, 不可以修改它, 比如 _x
这种形式
property
使用 @property
将变量标为只读属性, 使用如下.
class Student:
def __init__(self, name):
self.name = name
@property
def name(self):
return self.name
# 不可运行, 见下面解释
allen = Student('allen')
上面这里并不能运行, 因为 name
已经被设置为只读属性了, 所以在 __init__
中的赋值 (self.name = name
) 就会失败.
所以使用刚才所说的单下划线方式, 变化如下. 当然可以不用下划线, 使用自己规定的方式, 比如在前面加 private_xx.
class Student:
def __init__(self, name):
self._name = name # name --> _name
@property
def name(self):
return self._name # name --> _name
此外, 在类的内部, 可以直接使用 self.name
, 当然 self._name
也是可以的.
getattr
和 setattr
如果不仅仅是只有一个属性需要只读呢? 如果有一大波属性要为只读, 那么上一种方式就要用好多的 @property
.
使用 __setattr__
来解决这个问题, 这个方法是解释器如果找不到某个属性后调用的方法, 使用方法如下.
class Student:
readonly = ['score']
def __init__(self, score, age):
self._score = score
self.age = age
def __setattr__(self, name, value):
cls = type(self)
if name in cls.readonly:
raise AttributeError()
else:
super().__setattr__(name, value)
allen = Student(100, 20)
可以看到, 只要添加一个
readonly
数组, 然后在 __setattr__
中进行判断就可以了. 然后我们还可以添加 __getattr__
这个方法, 通过它就可以使用 self.score
进行访问, 否则每次都要 self._score
太麻烦了.
下面是实现的代码, 注意 __getattr__
和 __setattr__
最后的一行有所区别.
class Student:
readonly = ['score']
def __init__(self, score, age):
self._score = score
self.age = age
def __getattr__(self, name):
cls = type(self)
if name in cls.readonly:
name = '_' + name
return getattr(self, name)
def __setattr__(self, name, value):
cls = type(self)
if name in cls.readonly:
raise AttributeError()
else:
super().__setattr__(name, value)