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

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

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

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

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

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

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

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

但是,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_Database 是 DatabaseConnector 的内部方法,旨在供类中的其他方法(如 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 internallyaccount = 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 = Basederived = 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 methodt = Testt.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 namet._Test__private_symbol # Outputs: This is a private method.

对于类 Test 中名为 __private_symbol 的方法或变量,Python 会将名称修改为 _Test__private_symbol。这就是为什么当你使用 dir(Test) 检查类的目录时,你看到的是 而不是 __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 = 0: self.__balance = balance else: print("Balance cannot be negative.")

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

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

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

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

import pandas as pdclass 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 usageengagement_data = UserEngagementDataengagement_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 @property 装饰器和@method_name.setter代码说明

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

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

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

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

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

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

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

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

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

来源:自由坦荡的湖泊AI一点号

相关推荐