Python面向对象封装详解:单下划线与双下划线的区别及访问技巧
本文深入解析Python面向对象中的封装机制,详细对比单下划线与双下划线在属性及方法私有化上的行为差异。通过代码示例展示双下划线引发的名称改写机制,并介绍如何通过自定义公有方法或property装饰器安全地访问和修改私有成员,帮助开发者掌握规范的封装实践。
一 封装的概念
封装在现实生活中随处可见,例如电视机、电脑和手机等设备。用户通常只能观察到外观并调用其提供的功能,而无法直接触及内部复杂的硬件结构。这种设计防止了用户进行非规范操作导致设备故障。编程同样源于生活,在python中,对象的封装旨在对外暴露固定的访问接口,限制对内部私有属性和私有方法的直接访问。Python中的封装主要体现为类属性和类方法的私有化处理,具体表现将在后续章节中详细阐述。
二 _ 和__ 对属性和方法的私有化
1. 单下划线_
当类中的属性或方法以单下划线_开头时,按照编码约定,这表示该成员属于“保护”级别,意在提示外部调用者不应直接访问。然而,Python解释器并不会强制阻止这种访问,如果在代码中强行调用,程序不会抛出错误。
如下:
class A():
#_ 声明是保护属性和保护方法
_name = '张三'
def __init__(self):
self._age = 23
def _method(self):
print("我叫{},今年{}岁".format(self._name, self._age))
if __name__ == '__main__':
a = A()
#打印类A的dir
print(a.__dir__())
#访问保护变量和保护方法
print(a._name)
a._method()输出结果:
>>>\n['_age', '__module__', '_name', '__init__', '_method', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']\n张三\n我叫张三,今年23岁
从上述输出可以看出,以单下划线开头的属性和方法在类外部依然可以被访问。尽管技术上可行,但根据Python的编码规范,遇到此类成员时应避免在外部直接调用,以维持代码的可维护性和意图清晰。
2. 双下划线__
与单下划线不同,以双下划线__开头的属性和方法被视为类的私有成员。在类外部以及子类中,使用常规的属性或方法访问方式将无法直接读取或修改这些成员,程序会抛出异常。具体表现如下:
class B():
#__ 声明是私有化的
__name = '张三'
def __init__(self):
self.__age = 23
self.__luange = 'python'
def __method(self):
#私有方法
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
def fun(self):
#公有方法
print("this is a public method")
if __name__ == '__main__':
b = B()
#打印类B的dir
print(b.__dir__())
#访问类B的私有属性和私有方法
b.fun()
print(b.__name, b.__age, b.__luange)
b.__method()输出结果:
>>>\n['_B__age', '_B__luange', '__module__', '_B__name', '__init__', '_B__method', 'fun', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']\nthis is a public method\nTraceback (most recent call last):\n File "C:/Users/admin/python-learning/python学习文件/python基础/python类封装.py", line 56, in <module>\n print(b.__name, b.__age, b.__luange)\nAttributeError: 'B' object has no attribute '__name'
结果显示,访问类B的公有方法fun()可以正常执行,但尝试访问私有属性name时,程序报错指出类B中不存在名为__name的属性。回顾前文单下划线的情况,打印类A的dir()时,可以看到name属性和method方法在dir列表中保留了原始名称。

而在类B中,我们同样打印了其私有属性和私有方法的dir()结果,如下:

观察发现,所有以双下划线开头的私有属性和私有方法,在dir()输出中均被改写为_类名__属性或_类名__方法的形式(例如_B__age)。因此,直接使用__name或__age访问会失败。不过,如果显式使用_类名__属性(实例属性)或_类名__方法(类属性)的形式进行访问,依然可以成功获取值。具体操作如下:
class B():
#__ 声明是私有化的
__name = '张三'
def __init__(self):
self.__age = 23
self.__luange = 'python'
def __method(self):
#私有方法
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
def fun(self):
#公有方法
print("this is a public method")
if __name__ == '__main__':
b = B()
#打印类B的dir
print(b.__dir__())
#访问类B的私有属性和私有方法
b.fun()
print(B._B__name, b._B__age, b._B__luange)
b._B__method()结果如下:
>>>\n['_B__age', '_B__luange', '__module__', '_B__name', '__init__', '_B__method', 'fun', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']\nthis is a public method\n张三 23 python\n我叫张三,今年23岁,我喜欢python。
3. 子类中访问父类的私有属性和私有方法
子类无法直接通过常规方式访问父类的私有属性和私有方法,尝试访问将导致属性错误:
class B():
#__ 声明是私有化的
__name = '张三'
def __init__(self):
self.__age = 23
self.__luange = 'python'
def __method(self):
#私有方法
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
def fun(self):
#公有方法
print("this is a public method")
class C(B):
def __init__(self):
super().__init__()
def fun1(self):
#访问父类B的私有属性和私有方法
print(self.__name, self.__age, self.__luange)
self.__method()
if __name__ == '__main__':
c = C()
c.fun1()输出结果:
>>>\nAttributeError: 'C' object has no attribute '_C__name'\nAttributeError: 'C' object has no attribute '_C__method'
这表明子类无法直接访问父类的私有成员。
此外,当子类中定义的属性和方法与父类的私有成员同名时,并不会覆盖父类的私有成员。
class B():
#__ 声明是私有化的
__name = '张三'
def __init__(self):
self.__age = 23
self.__luange = 'python'
def __method(self):
#私有方法
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
def fun(self):
#公有方法
print("this is a public method")
class C(B):
__name = '李四'
def __init__(self):
super().__init__()
self.__age = 24
self.__luange = 'C++'
def fun1(self):
#访问父类B的私有属性和私有方法
print(self.__name, self.__age, self.__luange)
self.__method()
def __method(self):
#类C的私有方法,与父类方法同名,但不重写父类方法
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
#调用父类的私有方法
B()._B__method()
if __name__ == '__main__':
c = C()
#访问类C的私有方法
c._C__method()结果如下:
>>>\n我叫李四,今年24岁,我喜欢C++。\n我叫张三,今年23岁,我喜欢python。
从输出可见,子类C并未重写父类B的__method()方法。为了理解这一现象,我们可以打印类B和类C的dir()结果进行对比:
>>>\n['_B__age', '_B__luange', '_C__age', '_C__luange', 'fun1',\n '_C__method', '__doc__', '_B__name', '_B__method', '_C__name', ...]
结果显示,在类C的dir()中,父类B的私有成员以_B__属性(或方法)的形式存在,而类C自身的私有成员则以_C__属性(或方法)的形式存在。由于Python的双下划线机制会将名称改写为_类名__成员,父类与子类的同名私有成员在命名空间中被区分开来,因此子类不会覆盖父类的私有方法。
三 访问及修改类的私有属性和私有方法
通过私有化属性和方法,类实现了对内部数据的封装和保护。然而,当外部确实需要读取或修改这些数据时,该如何处理?这类似于电视机虽然内部封闭,但仍提供固定的外部接口供用户操作。\n虽然理论上可以通过_类名__属性或_类名__方法的形式访问私有成员,但这违反了Python的编程规范,属于非推荐做法,正如用户不应随意拆开电视机内部进行操作一样。
正确访问和修改私有属性及方法的常规手段主要有两种,具体如下:
1. 自定义公有方法
class D():
__name = '张三'
def __init__(self):
self.__age = 23
self.__luange = 'python'
def __method(self):
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
def get_value(self):
return self.__name, self.__age, self.__luange
def get_method(self):
self.__method()
def set_value(self, name, age, luange):
self.__name, self.__age, self.__luange = name, age, luange
if __name__ == '__main__':
d = D()
#通过get_value方法访问私有属性
print(d.get_value())
#通过get_method方法访问私有方法
print('=' * 30)
d.get_method()
#通过set_value方法修改私有属性
print('='*30)
print(d.get_value())
d.set_value('王二麻子', 25, 'Linux')
print(d.get_value())
d.get_method()输出结果:
>>>\n('张三', 23, 'python')\n==============================\n我叫张三,今年23岁,我喜欢python。\n==============================\n('张三', 23, 'python')\n('王二麻子', 25, 'Linux')\n我叫王二麻子,今年25岁,我喜欢Linux。
通过上述示例,我们利用自定义的get_value()、get_method()以及set_value()公有方法,成功实现了对私有属性和私有方法的访问与修改。
2. property
property装饰器主要具备以下两种作用:
- 作为装饰器,@property 将类的方法转换为只读的类属性
- property 重新实现一个属性的 getter 和 setter 方法
以下通过类E的示例进行说明:
class E():
__name = '张三'
def __init__(self):
self.__age = 23
self.__luange = 'python'
def __method(self):
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
def get_value(self):
return self.__name
def set_value(self, name):
self.__name = name
getValue = property(get_value, set_value)
@property
def get_method(self):
self.__method()
if __name__ == '__main__':
e = E()
#访问
print(e.getValue)
e.get_method
#修改
e.getValue = '王二'
print(e.getValue)结果:
>>>\n张三\n我叫张三,今年23岁,我喜欢python。\n王二
在上述代码中,我们将get_value和set_value方法传入property后,类方法被转换为类属性并赋值给getValue变量。此时,e.getValue表现为只读(对应get_value方法),而e.value = ‘王二’则触发修改操作(对应get_value方法)。同理,通过@property装饰get_method方法,也可将其转换为属性。
下面property展示了重新实现属性的getter和setter方法,这种写法相较于前一种更为常用。
class E():
__name = '张三'
def __init__(self):
self.__age = 23
self.__luange = 'python'
def __method(self):
print("我叫{},今年{}岁,我喜欢{}。".format(self.__name, self.__age, self.__luange))
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
if __name__ == '__main__':
e = E()
#访问
print(e.name)
#修改
print("修改前:", e.name)
e.name = '隔壁老王'
print("修改后:", e.name)输出结果:
>>>\n张三\n修改前: 张三\n修改后: 隔壁老王
上述代码首先将name方法交由property装饰器处理,随后调用装饰后的setter方法,从而实现对私有属性的修改。
到此这篇关于Python面向对象中的封装详情的文章就介绍到这了,更多相关Python封装内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













