Python - 封装与抽象


2021年8月23日, Learn eTutorial
2374

在本教程中,您将精通 Python 中关于封装和抽象的 OOP 概念,以及它们之间的区别。您还将详细了解面向对象编程中重要的访问修饰符,例如私有、公有和受保护,并附带示例。

在之前的教程中,您已经掌握了 Python 中继承的概念。如果您是 OOPS 新手,我们建议您学习我们的继承教程,以便熟悉 OOPS。

什么是封装

封装是面向对象编程 (OOP) 的重要基石之一,它允许将所有数据(属性和方法)封装到一个单一的组件(类)中,从而通过限制直接访问变量或方法来防止数据的意外修改。

Encapsulation

封装

该图让您了解类是封装的一个例子,它封装了其所有数据,即属性和方法。

在现实世界中,请考虑您的房屋作为一个应用程序,它由卧室、餐厅、厨房等组成,这些都可以看作是类。根据功能,您将不同的物品(数据)放在房屋的不同部分(类)中,例如卧室里的床、餐厅里的餐桌、厨房里的炉子。这就是现实生活中的封装。

封装通常被称为“数据隐藏”,因为它提供了一种访问控制机制。那么什么是数据隐藏呢?数据隐藏是指隐藏实体的某些数据,使其不被其他实体访问,从而防止数据被其他实体意外修改。

考虑将钱和珠宝等贵重物品放在家里的情况。实际上,我们把它们锁在保险箱里,对外保密。编程中的数据隐藏也是如此。

如何进行数据隐藏

访问修饰符是面向对象编程中不可或缺的元素,因为它们限制了对类中属性和方法的访问。与 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 中的抽象类和抽象方法是什么?

How abstraction can be achieved

如何实现抽象

**抽象类**是包含一个或多个抽象方法的类。一个类只有在包含至少一个抽象方法时才成为抽象类。如果一个方法被声明但未被定义,则称其为**抽象方法**。具体来说,**抽象方法**不包含任何实现。然而,所有的实现都可以定义在继承抽象类的子类的方法中。

另一点需要注意的是,默认情况下 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, in 
    ob = 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, in 
    obr = 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

关键要点是,抽象类包含普通方法以及抽象方法,并且我们无法实例化任何抽象类。

抽象与封装的区别

抽象和封装是相似的,因为数据的抽象可以通过封装来实现。尽管抽象和封装携手并进,但它们之间却有很大的不同。

抽象 封装
抽象隐藏不必要的数据,并向最终用户显示相关数据。 封装将数据和代码封装起来,防止外部世界滥用。
抽象在设计层面起作用 封装在应用程序层面起作用
抽象侧重于程序的思想 封装侧重于代码的功能或实现
关注“应该做什么” 关注“如何做”
抽象通过使用抽象类和抽象方法实现 封装使用下划线作为访问修饰符来保护代码免受外部实体的影响