Skip to content

流畅的 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 也是可以的.

getattrsetattr

如果不仅仅是只有一个属性需要只读呢? 如果有一大波属性要为只读, 那么上一种方式就要用好多的 @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)

Comments