封装是将属性和方法捆绑在一个单一单元内的过程。它是面向对象编程范式的主要支柱之一。
我们知道类是一个用户定义的对象原型。它定义了一组数据成员和能够处理这些数据的方法。
根据数据封装的原则,描述一个对象的数据成员对外部环境是隐藏的。它们只能通过同一类内的方法访问。另一方面,方法本身可以从类的外部访问。因此,可以说对象数据被方法封装了。通过这种方式,封装阻止了对对象数据的直接访问。
在 Python 中实现封装
像 C++ 和 Java 这样的语言使用访问修饰符来限制对类成员(即变量和方法)的访问。这些语言中有 public、protected 和 private 关键字来指定访问类型。
如果一个类成员可以从程序中的任何地方访问,则称其为公共成员。私有成员仅允许在类内访问。通常,方法被定义为公共的,而实例变量为私有的。这种私有实例变量和公共方法的安排确保了封装的实现。
与这些语言不同,Python 没有提供指定类成员可能具有的访问类型的方式。默认情况下,Python 类中的所有变量和方法都是公共的,如下例所示。
示例 1
这里,我们有一个带有实例变量 name 和 age 的 Employee 类。该类的对象具有这两个属性。由于它们是公共的,因此可以从类的外部直接访问它们。
class Student:
def __init__(self, name="Rajaram", marks=50):
self.name = name
self.marks = marks
s1 = Student()
s2 = Student("Bharat", 25)
print("Name: {} marks: {}".format(s1.name, s2.marks))
print("Name: {} marks: {}".format(s2.name, s2.marks))
它将产生以下输出:
Name: Rajaram marks: 50
Name: Bharat marks: 25
在上面的例子中,实例变量是在类内部初始化的。然而,从类外部访问实例变量的值没有任何限制,这违背了封装的原则。
尽管没有关键字来强制可见性,Python 有一个特殊的命名约定来命名实例变量。在 Python 中,通过单个或双下划线前缀变量/方法的名字来模仿受保护和私有访问修饰符的行为。
如果一个变量以单个双下划线开头(例如 "__age"),则该实例变量是私有的;类似地,如果变量名以单个下划线开头(例如 "_salary")。
示例 2
让我们修改 Student 类。添加另一个实例变量 salary。通过在它们前面加上双下划线使 name 私有并且 marks 也私有。
class Student:
def __init__(self, name="Rajaram", marks=50):
self.__name = name
self.__marks = marks
def studentdata(self):
print("Name: {} marks: {}".format(self.__name, self.__marks))
s1 = Student()
s2 = Student("Bharat", 25)
s1.studentdata()
s2.studentdata()
print("Name: {} marks: {}".format(s1.__name, s2.__marks))
print("Name: {} marks: {}".format(s2.__name, __s2.marks))
当你运行这段代码时,它将产生以下输出:
Name: Rajaram marks: 50
Name: Bharat marks: 25
Traceback (most recent call last):
File "C:\Python311\hello.py", line 14, in <module>
print("Name: {} marks: {}".format(s1.__name, s2.__marks))
AttributeError: 'Student' object has no attribute '__name'
上述输出清楚地表明,实例变量 name 和 age 可以由类内部声明的方法(studentdata() 方法)访问,但双下划线前缀使这些变量私有,因此从类外部访问它们是受限的,这会引发 AttributeError。
什么是名称改编?
Python 并没有完全阻止对私有数据的访问。它只是依赖于程序员的智慧,不在类外部编写任何访问它的代码。仍然可以通过 Python 的名称改编技术访问私有成员。
名称改编是将带有双下划线的成员名字更改成 object._class__variable 形式的过程。如果确实需要,仍然可以从类的外部访问它,但应避免这种做法。
在我们的例子中,私有实例变量 "__name" 通过将其更改成 obj._class__privatevar 的形式来进行改编。
因此,要访问 "s1" 对象的 "__marks" 实例变量的值,将其改为 "s1._Student__marks"。
更改上述程序中的 print() 语句为:
print(s1._Student__marks)
现在它打印出了 s1 的分数 50。
因此,我们可以得出结论,Python 并没有完全按照面向对象编程理论来实现封装。它采用了一种更为成熟的方式来处理这个问题,通过规定命名约定并在确实需要访问公共作用域中的私有数据时让程序员使用名称改编。