多使用公共属性,而不是私有属性
公有属性与私有属性
在Python中,对一个类而言只有两种可视的属性类型:公有的和私有的。
class MyObject(object):
def __init__(self):
self.public_field = 5
self.__private_field = 10
def get_private_field(self):
return self.__private_field
公有的属性可以通过对象加上一个点操作符来获取其值。
foo = MyObject()
assert foo.public_field == 5
私有的字段是使用双下划线在前面标注的。它们可以通过类内部定义的方法来直接的获取值,而不能像公有属性那样来获取。
assert foo.get_private_field() == 10
如果直接从类的外部访问私有的属性的话,就会触发一个异常,详细信息如下:
foo.__private_field
>>>
AttributeError: 'MyObject' object has no attribute '__private_field'.
而类方法可以访问类的私有属性的原因在于类方法本身被属于这个类的作用域范围内,所以有这个权限。
class MyOtherObject(object):
def __init__(self):
self.__private_field = 71
@classmethod
def get_private_field_of_instance(cls, instance):
return instance.__private_field
bar = MyOtherObject()
assert MyOtherObject.get_private_field_of_instance(bar) == 71
正如你预期的那样,子类也不能访问得到其父类的私有字段。
class MyParentObject(object):
def __init__(self):
self.__private_field = 71
class MyChildObject(MyParentObject):
def get_private_field(self):
return self.__private_field
baz = MyChildObject()
baz.get_private_field()
>>>
AttributeError: ;MyChildObject' objetc has no attribute '__private_field'.
访问私有属性
私有属性的行为需要有一个简单的转换才能被访问的到。当Python的解释器在MyChildObject.getprivatefield方法中看到私有的属性访问时,它就将__private_field
转换成访问_MyChildObject__private_field
。这时就可以像访问公有属性的方式来访问的到私有属性对应的值了。在这个例子中,__private_field
属性仅仅定义在了`MyParentObject.__init中了,这意味着这个私有属性的真实的名字是
_MyParentObject__private_field`。从子类来访问父类的私有属性肯定会失败咯,就是因为这个经历了这个转换过程后名称不匹配的缘故。
知道了这个梗,你就可以轻松愉快的访问任何类的私有属性的值了,不管是从子类,还是从类的外部,无需权限就可以访问哒。
assert baz._MyParentObject__private_field == 71
如果这个时候,你查看一下对象的属性字典的时候,你将会看到其实私有属性也是被存储在其中的。当然,存储的名称就是刚才经历了转换过程之后的名称了。
print(baz.__dict__)
>>>
{'_MyParentObject__private_field': 71}
为什么对于私有属性的语法没有严格的执行可视规则呢?最简单的原因就是一个Python中最常被引用的格言:"我们都是自愿的成年人"。Python程序员相信被公开的好处大于被关闭的缺点。
除此之外,能够访问属性的这种语言hook特征(详见第32项:为懒属性使用getattr,getattribute,setattr方法)可以让你对类的内部信息随时随地的查看。如果你想这样做,那么Python为什么还要阻止你对私有属性的访问呢?
为了减少对访问未知内部的损坏,Python程序员应该遵守风格知道里面的命名习惯(详见第2项:使用PEP8风格来座位编码的指导)。前面带有单个下划线的字段属于保护类型(像_protected_field
),意味着类外部的用户应该对其小心处理。
然而,许多Python初学者经常使用私有的字段来表示一个不应该被子类或者外部访问得到的内部的API。
class MyClass(object):
def __init__(self, value):
self.__value = value
def get_value(self):
return str(self.__value)
foo = MyClass(5)
assert foo.get_value == '5'
很明显这是一个错误的方法。