了解使用 Python 进行面向对象编程中的封装

2024年12月12日10:44:03 科技 1546

正如在了解 Python 中的面向对象编程中所看到的那样,面向对象编程 (OOP) 是一种使用“对象”来设计应用程序和计算机程序的范式。它利用几个关键概念(包括封装、继承和多态性)来提高代码的模块化和灵活性。在本文中,我们将重点介绍封装,这是 OOP 的一个基本方面,有助于实现数据隐藏和抽象。

了解使用 Python 进行面向对象编程中的封装 - 天天要闻

什么是封装?

封装是将对数据进行操作的数据 (属性) 和方法 (函数) 捆绑到一个单元(称为对象)中的机制。它限制对对象的某些组件的直接访问,这可以防止意外修改数据。要理解封装,让我们分解一下它的主要功能:

  • 数据隐藏:对象的内部状态对外界隐藏。这也称为信息隐藏。
  • 访问限制:外部代码无法直接访问对象的内部状态。相反,它必须使用对象提供的特定方法(如 getter 和 setter)来读取或修改其状态。
  • 简化:通过明确定义的接口与对象交互,降低了系统的复杂性,使其更易于理解和维护。

Python 中的封装

Python 的封装方法有些独特。与 C++Java 等语言不同,Python 没有 publicprivateprotected 等关键字来显式实施访问限制。

Python 对类数据和方法采用更开放的方法,基本上将所有 data 和 methods 视为公共的。对于那些习惯了其他语言的严格访问控制的人来说,这种设计选择似乎不合常规,但它体现了一个核心的 Python 理念:“我们都是同意的成年人。

“我们都是自愿的成年人”的原则超出了单纯的代码结构;它反映了整个 Python 社区的精神。它建议开发人员应该相互信任,以负责任地使用类属性和方法,而不是像 Java 等语言中那样,通过 privateprotected 等访问修饰符来强制实施严格的屏障

但是,Python 支持一种约定来实现类似的效果。

Python 中的命名约定

单前导下划线:_

第一个也是最广泛认可的约定是使用单个前导下划线 (_) 来表示不打算成为类的公共接口一部分的属性或方法。这些是内部实现,可能会发生变化,不应直接依赖外部代码。

class MyClass:
    def public_method(self):
        print("This is a public method")
    
    def _internal_method(self):
        print("This is an internal method")

在此示例中,_internal_method 旨在在 MyClass 内部或由子类使用,这表明它不是稳定的公共接口的一部分。虽然 Python 中没有任何内容阻止对 Python 的访问_internal_method,但下划线向其他开发人员发出了一个明确的信号,即应该谨慎使用。

单个下划线 (_) 用例

在此示例中,_connect_to_DatabaseDatabaseConnector 的内部方法,旨在供类中的其他方法(如 connect)使用,而不是从类外部直接访问。

class DatabaseConnector:
    def connect(self):
        self._connect_to_database()  # Internal use indicated by single underscore

    def _connect_to_database(self):
        print("Connecting to the database")

在 Python 中,单个下划线也用于表示变量是临时的或无关紧要的。这在循环或解包表达式中很常见,其中一个或多个值是有意未使用的。

for _ in range(5):
    print("Repeat action")

_, value = (1, 2)  # Only interested in the second value

这里, _ 用于忽略 loop 变量和 Tuples 中的第一个值,分别关注重复项和感兴趣的值。

双前导下划线:__

Python 还使用涉及双前导下划线 (__) 的命名约定来创建更强的隐私指示。这会触发一个称为名称修饰的功能,其中解释器以某种方式更改变量的名称,使其更难(但并非不可能)从类外部访问。

class MyClass:
    def __init__(self):
        self.__private_var = "This is a private variable"

    def __private_method(self):
        print("This is a private method")

使用名称修饰时,__private_var__private_method 在内部分别重命名为 _MyClass__private_var_MyClass__private_method。此机制不会使属性或方法真正私有,但确实表明了更强的非公共访问意图,从而有效地阻止了来自类外部的直接访问。其目的是创建不应直接从类外部访问的“私有”成员的更强指示,从而保护其内部状态和行为。

双下划线 (__) 用例

在此示例中,__balance只能通过 Account 类的方法(如 deposit)进行访问,以保护帐户余额的完整性。

class Account:
    def __init__(self, balance):
        self.__balance = balance  # Name mangling to make it harder to access directly

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount  # Accessing the mangled name internally

account = Account(100)
# Direct access to '__balance' would fail; Python mangles the name to '_Account__balance'

双下划线还可用于避免子类中的命名冲突,这些冲突可能会无意中覆盖基类属性或方法。

class Base:
    def __init__(self):
        self.__hidden = 'Base'

class Derived(Base):
    def __init__(self):
        super().__init__()
        self.__hidden = 'Derived'  # Does not actually override 'Base.__hidden'

base = Base()
derived = Derived()
# 'Base.__hidden' and 'Derived.__hidden' are two different attributes due to name mangling.

在这里,Base 类和 Derived 类中的 __hidden 以不同的方式进行分解,确保 Derived 类的 __hidden 属性不会覆盖 Base 类的 __hidden 属性,从而防止意外的副作用。

总之,在 Python 中使用单下划线 (_) 和双下划线 (__) 有不同的目的。单个下划线是内部使用或忽略值的提示,促进了一种基于约定的方法来指示预期的使用范围。双下划线通过名称修饰,为表示 “私人” 成员提供了更强的屏障,避免了命名冲突,符合“我们在这里都是成年人”的原则,同时仍然保护了类的内部运作。

深入研究其他一些示例,以更好地了解 Python 中封装的工作原理。

class Test:
    def __private_symbol(self):
        print("This is a private method.")

    def normal_symbol(self):
        print("This is a normal method.")

# Accessing the public method
t = Test()
t.normal_symbol()  # Outputs: This is a normal method.

# Attempting to access the private method directly will result in an AttributeError
# t.__private_symbol()  # Uncommenting this line will raise an error

# However, the private method can still be accessed through its mangled name
t._Test__private_symbol()  # Outputs: This is a private method.

对于类 Test 中名为 __private_symbol 的方法或变量,Python 会将名称修改为 _Test__private_symbol。这就是为什么当你使用 dir(Test) 检查类的目录时,你看到的是 _Test__private_symbol 而不是 __private_symbol 的 marmed 名称。

了解公约

  • 私人会员:应被视为 API 的非公共部分。它们旨在成为课程的内部内容,如有更改,恕不另行通知。因此,不建议在外部使用这些成员,以保持代码兼容性并防止意外误用。
  • 名称修饰: 此机制并非旨在安全地隐藏信息。相反,这是一种帮助避免子类中的命名冲突的方法,这些冲突可能会无意中覆盖 superclasses 的私有成员。

考虑一个表示银行帐户的简单类 BankAccount

class BankAccount:
    def __init__(self, account_number, balance=0):
        self.__account_number = account_number  # private attribute
        self.__balance = balance  # private attribute

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"Amount {amount} deposited successfully.")
        else:
            print("Deposit amount must be positive.")

    def withdraw(self, amount):
        if amount > 0 and amount <= self.__balance:
            self.__balance -= amount
            print(f"Amount {amount} withdrawn successfully.")
        else:
            print("Insufficient balance or invalid withdrawal amount.")

    def get_balance(self):
        return self.__balance

    def set_balance(self, balance):
        if balance >= 0:
            self.__balance = balance
        else:
            print("Balance cannot be negative.")

在此示例中,BankAccount 类封装了 __account_number__balance 属性,使它们成为私有的。这可以防止从类外部直接访问,从而强制执行封装。该类提供了 deposit()、withdraw() 和 getter/setter 方法等公共方法,供 __balance 与这些私有属性进行交互。

数据科学上下文中的封装:Python 示例

在数据科学中,封装可能特别有用,例如以安全和结构化的方式管理和操作数据集。让我们考虑一个在数据科学项目中应用封装的实际示例。

假设我们正在使用一个数据集来跟踪网站的用户参与度指标。我们的目标是将数据集及其操作方法封装在一个类中,为数据操作提供一个清晰简单的接口,同时隐藏数据处理的复杂性。

示例:User Engagement Metrics 类

我们将创建一个类 UserEngagementData,用于封装网站的用户参与度数据。本课程将提供添加新数据、计算平均参与度指标和检索特定数据点的方法,同时保持原始数据的私密性。

import pandas as pd

class UserEngagementData:
    def __init__(self):
        # Initialize a private DataFrame to store user engagement data
        self.__data = pd.DataFrame(columns=['user_id', 'page_views', 'time_spent'])

    def add_engagement_data(self, user_id, page_views, time_spent):
        """Add a new record of user engagement data."""
        new_data = {'user_id': user_id, 'page_views': page_views, 'time_spent': time_spent}
        self.__data = self.__data.append(new_data, ignore_index=True)

    def average_engagement(self):
        """Calculate average page views and time spent across all users."""
        if not self.__data.empty:
            return {
                'average_page_views': self.__data['page_views'].mean(),
                'average_time_spent': self.__data['time_spent'].mean()
            }
        else:
            return {'average_page_views': 0, 'average_time_spent': 0}

    def get_user_engagement(self, user_id):
        """Retrieve engagement data for a specific user."""
        user_data = self.__data[self.__data['user_id'] == user_id]
        if not user_data.empty:
            return user_data.iloc[0].to_dict()
        else:
            return "User data not found."

# Example usage
engagement_data = UserEngagementData()
engagement_data.add_engagement_data(user_id=1, page_views=5, time_spent=120)
engagement_data.add_engagement_data(user_id=2, page_views=3, time_spent=80)

print(engagement_data.average_engagement())
print(engagement_data.get_user_engagement(1))

在此示例中,UserEngagementData 类封装了用户参与度数据集(存储为 pandas DataFrame),并提供与此数据交互的公共方法。

此设计具有以下几个优点:

  • 数据完整性:通过限制对数据集的直接访问,我们可以防止外部代码的意外修改,从而确保数据集的完整性。
  • 简单性:该类的用户不需要了解底层数据结构 (pandas DataFrame) 或用于计算平均值的逻辑。他们通过简单、直观的方法与数据集进行交互。
  • 灵活性:如果我们决定更改内部实现(例如,切换到不同的数据存储机制),我们可以在不影响使用此类的代码的情况下这样做。

利用属性装饰器和 @method_name.setter 在 Python 中进行封装

在 Python 中,@property 装饰器是一个内置的装饰器,它允许我们在类中定义可以像属性一样访问的方法。此功能对于实现封装特别有用,提供了一种使用 getter 和 setter 来管理对私有变量的访问的 Pythonic 方法。

了解属性修饰器

@property 装饰器将类方法转换为属性。这意味着可以像访问属性一样访问该方法,而无需像调用函数一样调用它。这特别有用,主要有两个原因:

  • Getter 方法:通过使用 @property 修饰方法,您可以访问 private 属性,而无需直接公开它。
  • Setter Method:通过使用 @setter 装饰器,您可以定义一个允许您设置属性值的 setter 方法。此方法可以包括用于验证或修改正在设置的数据的逻辑,从而控制如何修改属性。

示例:对用户配置文件使用属性修饰器

让我们创建一个类 UserProfile,它使用属性修饰器来管理对用户年龄的访问,确保只能分配有效的年龄。

class UserProfile:
    def __init__(self, name, age):
        self.name = name
        self.__age = age  # Private attribute

    @property
    def age(self):
        """Getter method for age."""
        return self.__age

    @age.setter
    def age(self, value):
        """Setter method for age with validation."""
        if isinstance(value, int) and 0 < value < 150:
            self.__age = value
        else:
            raise ValueError("Age must be an integer between 1 and 149.")

# Example usage
user = UserProfile("John Doe", 25)
print(user.age)  # Accesses the getter method

user.age = 30  # Accesses the setter method
print(user.age)

# Trying to set an invalid age
try:
    user.age = -5
except ValueError as e:
    print(e)  # This will raise the ValueError defined in the setter method

@property 装饰器和@method_name.setter代码说明

在此示例中,UserProfile 类具有从外部封装的 private 属性 __age@property 修饰的 age 方法充当 getter,允许我们在不直接访问私有变量的情况下检索用户的年龄。

@age.setter 方法允许设置 __age 的值,还有一个额外的好处是能够包含验证逻辑以确保 age 在合理范围内。如果指定的对象是指定的类型,则 isinstance() 函数返回 True。验证的另一部分是 Age range (年龄范围) 的检查。

这种使用属性装饰器和 @method_name.setter 进行封装,不仅保护了数据的完整性,还为与类属性的交互提供了一个清晰直观的界面。它封装了类的内部工作原理,仅向外部世界公开必要和安全的内容。

Python 中的 @property 装饰器提供了一种优雅而有效的方法来实现封装。通过提供对私有变量的受控访问机制,它有助于维护数据的完整性,同时使类更易于使用和维护。这种封装方法与面向对象编程的原则非常一致,强调数据隐藏和接口的重要性。

如果您想了解有关 @property装饰器的更多信息:

了解 Python 中的@property装饰器

Python 最强大的装饰器

为什么使用封装?

封装具有以下几个好处:

  • 安全性:敏感数据隐藏起来,不让外部访问,从而降低了意外修改的风险。
  • 简单性:对象的客户端不需要了解其内部复杂性。他们通过一个简单的界面与之交互。
  • 模块化:将数据和操作封装在对象中使代码更加模块化且更易于维护。

封装是一个强大的 OOP 概念,可以显著增强数据科学项目的结构和安全性。通过将数据和操作封装在类中,我们创建了一个更加模块化、可维护且易于使用的代码库。这种方法在数据管理科学中特别有用,因为在数据科学中,管理复杂的数据集和操作很常见。

封装是面向对象编程中的一个强大概念,它有助于以捆绑数据和操作的方式构建代码,限制对内部状态的访问,并为交互提供清晰的接口

在 Python 中,尽管封装不是由特定于语言的关键字强制执行的,但使用属性修饰器在属性名称前加上双下划线的约定可以有效地实现此目的。了解和实施封装可以带来更安全、模块化和可维护的代码库。

科技分类资讯推荐

雷军,职务调整! - 天天要闻

雷军,职务调整!

5月5日,雷军职务调整冲上微博热搜。天眼查显示,近日,小米之家商业有限公司发生工商变更,雷军由执行董事改任董事,同时经营范围新增智能家庭消费设备销售、美发饰品销售。
雷军2025年遭老罪了!卸任董事又遇车祸,小米这回还能挺住不? - 天天要闻

雷军2025年遭老罪了!卸任董事又遇车祸,小米这回还能挺住不?

2025年5月,小米之家工商信息悄摸儿变了——雷军的职务从“执行董事”改成“董事”。就这俩字的变动,跟往舆论锅里扔了颗炸弹似的,炸出一堆问号:雷总这是要放权了?小米是不是出啥大事儿了?咱今儿就掰开揉碎了,聊聊雷军今年有多难,顺便看看小米这船
个人视频被搬运上热搜阅读过亿,“泼天的流量”令创作者害怕:不想被身边人看到 - 天天要闻

个人视频被搬运上热搜阅读过亿,“泼天的流量”令创作者害怕:不想被身边人看到

近日,短视频创作者“大福在成长”发布自己失业后生活的视频被搬运至其他平台,不仅上了热搜,话题量还破亿。“泼天的流量”却并未让她开心,因为自己本人并未授权,同时个人隐私还被“广而告之”,她私信要求对方删除却未被理睬。近日,创作者向扬子晚报/紫牛新闻记者讲述了自己艰难的维权过程。2024年11月份,博主“大福...
苹果推出 2025 彩虹系列Apple Watch表带 - 天天要闻

苹果推出 2025 彩虹系列Apple Watch表带

IT之家 5 月 5 日消息,苹果今日推出新款 Apple Watch 彩虹版运动型表带、表盘和 iPhone 与 iPad 墙纸。彩虹版运动型表带即日起接受订购,配套的动态表盘与墙纸近日将随软件更新发布。每一条彩虹版运动型表带都由手工装配而成,压模成型的鲜艳条纹构成形状大小不一的彩虹条状图案,呈现含蓄而又惊艳的多变效果。苹果称每一...
华生科技2024年财报亮眼,营收利润双增长,但研发项目延期引关注 - 天天要闻

华生科技2024年财报亮眼,营收利润双增长,但研发项目延期引关注

5月4日,华生科技发布2024年年报,公司实现营业收入3.52亿元,同比增长46.94%;归属净利润4942.30万元,同比增长77.91%。尽管业绩显著改善,但研发中心建设项目延期至2025年6月,引发市场对其执行力的质疑。一、业绩大幅回升,但仍未恢复至历史高点2024年,华生科技的营业收入和净利润均实现大幅增长,分别达到3.52亿元和4...