Effective Python 读书笔记 - 第27条:多用public属性,少用private属性

与C++和Java不同的是,对Python类来说,其属性的可见度只有两种,也就是publicprivate

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

    def get_private_field(self):
        return self.__private_field

任何人都可以在对象上通过dot操作符(即.操作符)来访问类的public属性。

foo = MyObject()
assert foo.public_field == 5

由两个下划线开头,且结尾最多有一个下划线(或没有)的属性,是private字段。本类的所有方法可以直接访问它们。

assert foo.get_private_field() == 10

在类的外面直接访问它的private字段会引发异常。

foo.__private_field

>>>
AttributeError: 'MyObject' object has no attribute '__private_field'

由于类的class method仍然声明在本类的class代码块之内,所以这些方法也是可以直接访问类的private属性的。

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

和C++/Java一样,Python中子类无法访问父类的private字段。

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' object has no attribute
'_MyChildObject__private_field'

这是因为Python会对私有属性的名称(以两个下划线开头的属性名称)做一些简单的变换,以保证"private"字段的私密性。当编译器看到MyChildObject.get_private_field()方法要访问私有属性时,它会先把__private_field变换为_MyChildObject__private_field,然后再进行访问。本例中,__private_field字段只在MyParentObject.__init__()里面做了定义,因此这个私有属性的真实名称,实际上是_MyParentObject__private_field(保存在baz.__dict__中)。

print(baz.__dict__)
assert baz._MyParentObject__private_field == 71

>>>
{'_MyParentObject__private_field': 71}

Python为什么不从语法上严格保证private字段的私密性呢?用最简单的话来说,就是:We are all consenting adults here. 这句广为流传的格言,表达了很多Python程序员的观点,即开放要比封闭好

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

为了尽量减少无意间访问内部属性所带来的意外,Python程序员会遵照《Python风格指南》的建议,用一种习惯性的命名方式来表示这种字段。也就是说,以单个下划线开头的字段,应该视为"protected"字段,本类之外的那些代码在使用这种字段时要多加小心。 

虽说有了这种约定,仍然有许多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'

这种写法不好。因为包括你自己在内的许多开发者,以后可能都需要从这个类中继承子类,并在子类中添加新的行为,或是改进现有方法中效率不高的那些部分(例如,MyClass.get_value()方法总是返回字符串,这可能就需要改进)。假如超类使用了"private"属性,那么子类在覆写或扩展时,就会显得既笨拙又容易出错。其实那些潜在的子类在迫不得已时,仍然能够访问那些"private"字段:

class MyIntegerSubclass(MyClass):
    def get_value(self):
        return int(self._MyClass__value)

foo = MyIntegerSubclass(5)
assert foo.get_value() == 5

上面的代码写法缺陷很明显,一旦MyClass增加了一个父类并将self.__value的定义移到MyClass的父类中,MyIntegerSubclass中的绝对引用就会失效。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值