本文整理自《Effective Python 编写高质量 Python 代码的 59 个有效方法》第 27 条:多用 public 属性,少用 private 属性

三种属性

python 类的成员变量中按可见度可划分为 public、protected 以及 private 属性

class MyObject(object):
    def __init__(self):
        self.public_field = 5
        self._protect_filed = 8
        self.__private_field = 10

    def get_private_filed(self):
        return self.__private_field


foo = MyObject()
# 访问 public 属性
print(foo.public_field)
# 访问 protected 属性
print(foo._protect_filed)
# 访问 private 属性会报错
# AttributeError: 'MyObject' object has no attribute '__private_field'
print(foo.__private_field)

public 属性

任何人都可以在对象上通过点操作符来访问 public 属性

foo = MyObject()
foo.public_field

protected 属性

以单个下划线开头的属性,这是为了尽量减少无意间访问内部属性所带来的意外,用一种习惯性的命名方式来表示该字段受保护,本类之外的代码使用该字段时要小心。

它本质上与 public 属性使用相同,但命名上体现了保护目的。

应该多用 protected 属性,并在文档里把这些字段的合理用法告诉子类开发者,而不要试图用 private 属性来限制子类访问这些字段。

class MyObject(object):
    def __init__(self):
        self.public_field = 5
        self._protect_filed = 8
        self.__private_field = 10

    def get_private_filed(self):
        return self.__private_field


class ChildClass(MyObject):
    def __init__(self):
        super().__init__()
        self._value = 10


childObj = ChildClass()
print(childObj._value)

print(childObj.public_field)
print(childObj._protect_filed)

应该主观上避免对 protected 属性的访问,但访问它也不会导致报错

private 属性

以两个下划线开头的属性,是 private 字段。在类的外面直接访问它会引发异常:

# 访问 private 属性会报错
# AttributeError: 'MyObject' object has no attribute '__private_field'
foo.__private_field

但本类的方法可以直接访问它们:

foo.get_private_filed()

子类无法访问父类的 private 字段:

print(childObj.__private_filed)
#AttributeError: 'ChildClass' object has no attribute '__private_filed'

其原理是 Python 对 private 属性的名称做了一些变换:比如 MyObject 的 __private_field 字段,实际上被变换成 _MyObject__private_filed 字段,通过变换后属性名与被访问属性名不相符达到类之外或子类无法访问 private 属性目的。

class MyObject(object):
    def __init__(self):
        self.public_field = 5
        self._protect_filed = 8
        self.__private_field = 10

    def get_private_filed(self):
        return self.__private_field


class ChildClass(MyObject):
    def __init__(self):
        super().__init__()
        self._value = 10


foo = MyObject()
print(foo._MyObject__private_field)
childObj = ChildClass()
print(childObj._MyObject__private_field)

换句话说,Python 编译器无法严格保证 private 字段的私密性。

Python 为什么不从语法上严格保证 private 字段的私密性呢?用最简单的话讲,We are all consenting adults here(我们都是成年人了)。这也是很多 Python 程序员的观点,大家都认为开放要比封闭好。

另外一个原因在于 Python 语言本身就已经提供了一些属性挂钩(__getattr__ 等),使得开发者能够按照自己的需要来操作对象内部的数据。既然如此,那为什么还要阻止访问 private 属性呢?

最后,不要盲目地将属性设为 private,而是应该从一开始就做好规划,并允许子类更多地访问超类的内部 API;只有当子类不受自己控制时,才考虑用 private 属性来避免命名冲突。