python学习笔记013——模块中的私有属性

时间:2023-03-08 21:42:23
python学习笔记013——模块中的私有属性

1 私有属性的使用方式

在python中,没有类似private之类的关键字来声明私有方法或属性。若要声明其私有属性,语法规则为:

属性前加双下划线,属性后不加(双)下划线,如将属性name私有化,则 __name 即可。

(实际上,属性前加单下划线,属性后不加下划线也可以 _name )


1)以一个下划线开头的标识符(_xxx),不能访问的方法或属性,但可通过类提供的接口进行访问, 不会被语句 from module import * 语句加载。

  单下划线开头的方式或属性:弱”内部使用“标识,如:”from M import *”,将不导入所有以下划线开头的对象,包括包、模块、成员

  只是为了避免与python关键字的命名冲突

2)以两个下划线开头的标识符(__xxx),不能被外部访问的方法或属性,不会被语句 from module import * 语句加载,外部访问时会报错。

  双下划线开头的方法或属性:模块内的成员,表示私有成员,外部无法直接调用


def hello1():
    pass

def _hello2():
    pass

def __hello3():
    pass

name1 = "name1"
_name2 = "name2"
__name3 = "name3"

print("该模块已被加载!")

在交互模式下运行

>>> from test import *
该模块已被加载!
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 

'hello1', 'name1']

可知,仅加载了 hello1 方法、 name1 属性

实际上,这种方式并不是真正私有,而是”伪私有“,也即伪私有属性;

它是通过变量名压缩(mangling)来实现变量名局部化。变量名压缩的规则:在初始的变量名头部加一个下划线,再加上类的名称,最后是初始变量名的名称。

2 为什么是伪私有属性

class Action(object):
    def __func(self):
        pass

if __name__ == '__main__':
    print(Action.__dict__)

类 Action 中有私有方法 __func 。

代码运行

{'__dict__': <attribute '__dict__' of 'Action' objects>, '_Action__func': <function Action.__func at 0x7f2f6b102c80>,'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Action' objects>}

通过类的__dict__属性类class Action 的所有属性打印出来,发现原定义的私有属性(方法)__func不存在,而是以_Action__func的形式存在于类中,也即方法__func变量名被压缩;,会自动进行扩展,从而包含所在类的名称。我们可以通过_Action__func来调用该类中的方法,这就是所谓的伪私有属性

通过修改调用形式就可以实现私有属性的外部调用:

class Action(object):
    def __func(self):
        print("__func私有方法被调用")

if __name__ == '__main__':
    a = Action()
    a._Action__func()
    print("----------------------")
    b = Action()
    b.__func()

运行

__func私有方法被调用
----------------------
Traceback (most recent call last):
  File "test.py", line 10, in <module>
    b.__func()
AttributeError: 'Action' object has no attribute '__func'

3  伪私有属性的意义

Python之所以支持变量名压缩(mangling),让类内部某些属性和函数局部化,使方法或属性无法在外部访问。这是因为私有属性被自动扩展为含有所在类的名称。

__membername 被系统替换成了 _classname__membername 外部使用原来的私有成员的名字时,会提示找不到。

伪私有属性是为了缓和与实例属性存储方式有关的问题。在Python中,所有实例的属性都会在类树底部的单个实例对象中。

在Python的类方法中,每当方法赋值self的属性时(如self.attr = value),就会在该实例内修改或创建该属性(继承搜索只发生在引用时,而不是赋值时)。

即使在这个层次中有多个相同的类赋值属性,也是如此。因此有可能发生冲突。

伪私有属性在较大的框架和工具中非常有用,既避免引入可能在类树中某处偶然隐藏定义的新的方法名,也可以减少内部方法被树的较低处的名称替代的机会。

如果一个属性或方法倾向于只在一个类中使用,在前面使用双下划线,一确保不受到继承树中其它名称的干扰,尤其是在多继承的环境中。

简而言之:使用伪私有属性可以解决变量名相互覆盖问题,也即  使用伪私有属性是为了避免在类树中,多个类赋值相同的属性引发冲突问题。

示例

假设有两个类,C1 和 C2,他们都有相同的属性X。

类C1

class C1():
    def meth1(self):
        self.x = 'Hello World'
    def meth2(self):
        print(self.x)
c1 = C1()
c1.meth1()
c1.meth2()

类C2

class C2():
    def meth3(self):
        self.x = 'Hello Python'
    def meth4(self):
        print(self.x)
c2 = C2()
c2.meth3()
c2.meth4()

类C1和C2在单独调用时,均能符合预期地输出

调用meth2方法时,打印meth1的赋值结果;

调用meth4方法时,打印meth3的赋值结果。

此时增加一个新的类C3,继承自C1、C2(多重继承):

class C1():
    def meth1(self):
        self.x = 'Hello World'
    def meth2(self):
        print(self.x)

class C2():
    def meth3(self):
        self.x = 'Hello Python'
    def meth4(self):
        print(self.x)

class C3(C1, C2):
    pass

c3 = C3()
c3.meth1()
c3.meth3()
c3.meth2()
c3.meth4()

从运行结果可以看出,每次 print(self.x)的内容,取决于 self.x 最后一次赋值的内容。存在赋值覆盖问题

Hello Python
Hello Python

在使用伪私有属性后可以解决变量名self.x相互覆盖的问题(因为self.__x 被压缩成了 self._C1__x 和 self._C2__x,变量名不同,不会互相覆盖):

class C1():
    def meth1(self):
        self.__x = 'Hello World'
    def meth2(self):
        print(self.__x)

class C2():
    def meth3(self):
        self.__x = 'Hello Python'
    def meth4(self):
        print(self.__x)

class C3(C1, C2):
    pass

c3 = C3()
c3.meth1()
c3.meth3()
c3.meth2()
c3.meth4()

运行结果符合C1的设计初衷:调用meth2时应该打印出meth1的赋值结果:

Hello World
Hello Python

参考:Python的伪私有属性    Python高级特性:私有属性