在本教程中,您将精通 Python 中关于封装和抽象的 OOP 概念,以及它们之间的区别。您还将详细了解面向对象编程中重要的访问修饰符,例如私有、公有和受保护,并附带示例。
在之前的教程中,您已经掌握了 Python 中继承的概念。如果您是 OOPS 新手,我们建议您学习我们的继承教程,以便熟悉 OOPS。
封装是面向对象编程 (OOP) 的重要基石之一,它允许将所有数据(属性和方法)封装到一个单一的组件(类)中,从而通过限制直接访问变量或方法来防止数据的意外修改。
该图让您了解类是封装的一个例子,它封装了其所有数据,即属性和方法。
在现实世界中,请考虑您的房屋作为一个应用程序,它由卧室、餐厅、厨房等组成,这些都可以看作是类。根据功能,您将不同的物品(数据)放在房屋的不同部分(类)中,例如卧室里的床、餐厅里的餐桌、厨房里的炉子。这就是现实生活中的封装。
封装通常被称为“数据隐藏”,因为它提供了一种访问控制机制。那么什么是数据隐藏呢?数据隐藏是指隐藏实体的某些数据,使其不被其他实体访问,从而防止数据被其他实体意外修改。
考虑将钱和珠宝等贵重物品放在家里的情况。实际上,我们把它们锁在保险箱里,对外保密。编程中的数据隐藏也是如此。
访问修饰符是面向对象编程中不可或缺的元素,因为它们限制了对类中属性和方法的访问。与 C、Java 等语言不同,Python 没有特定的访问修饰符。Python 作为一种面向对象的编程语言,利用**下划线**来执行其他语言中访问修饰符的功能。
在 Python 中,封装的方法略有不同。根据对象属性在类中的可见性,属性分为两类:**私有**和**公有**。如果一个数据成员或成员函数可以从程序的任何部分访问,则它被认为是公有的;如果它的可见性仅限于定义的类,则它被认为是私有的。任何以双下划线为前缀的数据或成员函数都是该类的私有成员。
下面的例子将为您清晰地展示封装。
class Access:
def __init__(self):
#public attribute
self.name= input('Enter your name :')
#private attribute
self.__secretcode=input("Enter your secret code :")
def permit(self):
if self.name =='abc' and self.__secretcode =='123!':
print("Access Granted" )
else:
print('Access Denied')
ob = Access()
ob.permit()
print(ob.name) #public attribute is visible to external world
print(ob._secretcode) #private attribute is invisible to external world
这个例子是登录验证的一个简化形式。在这里,属性 **name** 被表示为**公有**,而属性 secretcode 是**私有**的。您可以看到 `permit()` 方法检查 name 和 secretcode 是否满足条件。如果条件满足,则打印“Access granted”消息,否则打印“access denied”消息。这展示了登录的后端过程。之后,如果您尝试通过类的对象检索属性 **name** 和 **secret code**,您将获得 name,但 secret code 会导致错误。
以下是示例的输出
Enter your name :abc
Enter your secret code :123!
Access Granted
abc
Traceback (most recent call last):
File "oops_ex.py", line 211, in
print(ob._secretcode)
AttributeError: 'Access' object has no attribute '_secretcode'
OR
Enter your name :klm
Enter your secret code :1234
Access Denied
klm
Traceback (most recent call last):
File "oops_ex.py", line 211, in
print(ob._secretcode)
AttributeError: 'Access' object has no attribute '_secretcode'
通过观察输出,您会注意到,在两种情况下,当您尝试在外部访问 `__secretcode` 属性时,都会出现一个 AttributeError,提示 `__secretcode` 不存在。这是通过一种名为名称修饰(name mangling)的过程实现的,在该过程中,所有私有属性都会在内部被改名,前面加上类名。例如,`attribute __secretcode` 在内部会变成 `_Access__secretcode`。这样就将 secret code 对外部世界隐藏起来,实现了数据隐藏。
您可以通过运行 dir() 来获取这些私有属性,如下面的示例所示。
print(dir(ob))
['_Access__secretcode', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'permit']
这使得可以通过 object._classname__privateattributename 来访问这些私有属性。更改上面程序的最后一行会得到以下输出:
print(ob.name) #public attribute is visible to external world
print(ob._Access__secretcode) #private attribute is invisible to external world
输出
Enter your name :abc Enter your secret code :123! Access Granted abc 123!
与封装一样,抽象也是面向对象编程的基本构建块之一。
抽象是隐藏某个过程的内部实现,只向外部世界展示数据的基本特征,以减少复杂性并提高效率。在编程的上下文中,将方法的签名与其定义隔离开来称为抽象。它通常侧重于思想而不是功能。
在我们的日常生活中,我们使用电视、冰箱、洗衣机等电器来满足某些特定需求。我们不知道它们的内部工作原理,或者我们不关心。我们只是操作它们,它们的内部实现对我们来说是抽象的。
Python 的抽象可以通过在程序中使用抽象类和抽象方法来实现。那么,Python 中的抽象类和抽象方法是什么?
**抽象类**是包含一个或多个抽象方法的类。一个类只有在包含至少一个抽象方法时才成为抽象类。如果一个方法被声明但未被定义,则称其为**抽象方法**。具体来说,**抽象方法**不包含任何实现。然而,所有的实现都可以定义在继承抽象类的子类的方法中。
另一点需要注意的是,默认情况下 Python 不支持任何抽象类,但我们可以通过从 **abc** 模块导入名为 **ABC**(Abstract Base Class)的类来实现。创建抽象类的语法如下。
from abc import ABC
class class_name(ABC):
让我们来看下面的例子,以了解技术世界中的抽象概念。
from abc import ABC
class Shape(ABC): #abstract class
def area_calc(self): #abstract method
pass
这展示了抽象类的创建。这里,**Shape** 是一个抽象类,其中包含抽象方法 `area_calc()`。`area_calc()` 方法在这里没有被定义,而是使用了 pass 语句。所以这个方法被声明了但未被定义。如果我们尝试为抽象类创建对象会发生什么?请看下面的例子。
from abc import ABC
class Shape(ABC): #abstract class
def area_calc(self): #abstract method
pass
ob = Shape()
输出错误
Traceback (most recent call last): File "oops_ex.py", line 225, inob = Shape() TypeError: Can't instantiate abstract class Shape with abstract methods area_calc
现在,让我们添加继承了抽象类 Shape 的子类 **Rect**。如果在子类的方法中未定义实现会发生什么?
from abc import ABC
class Shape(ABC): #abstract class
def area_calc(self): #abstract method
pass
class Rect(Shape):
pass
obr = Rect()
输出错误
Traceback (most recent call last): File "oops_ex.py", line 226, inobr = Rect() TypeError: Can't instantiate abstract class Rect with abstract methods area_calc
您将收到一个 **TypeError**,提示我们无法实例化类 **Rect**。因此,这是强制要求定义该方法,因为 **Rect** 是继承抽象类的子类。所有的实现都可以定义在 **Rect** 类的 `area_calc()` 方法中。请仔细观察示例,了解抽象是如何工作的。
from abc import ABC
class Shape(ABC): #abstract class
def area_calc(self): #abstract method
pass
class Rect(Shape):
l=float(input('Enter length :'))
b=float(input('Enter breadth :'))
def area_calc(self):
return(self.l*self.b)
obr = Rect()
print("Area of Rectangle is ",obr.area_calc())
输出
Enter length :10 Enter breadth :20 Area of Rectangle is 200.0
关键要点是,抽象类包含普通方法以及抽象方法,并且我们无法实例化任何抽象类。
抽象和封装是相似的,因为数据的抽象可以通过封装来实现。尽管抽象和封装携手并进,但它们之间却有很大的不同。
| 抽象 | 封装 |
|---|---|
| 抽象隐藏不必要的数据,并向最终用户显示相关数据。 | 封装将数据和代码封装起来,防止外部世界滥用。 |
| 抽象在设计层面起作用 | 封装在应用程序层面起作用 |
| 抽象侧重于程序的思想 | 封装侧重于代码的功能或实现 |
| 关注“应该做什么” | 关注“如何做” |
| 抽象通过使用抽象类和抽象方法实现 | 封装使用下划线作为访问修饰符来保护代码免受外部实体的影响 |