跳至主要内容

深入解析 Python 私有方法

了解 Python 中的私有方法、其语法、如何以及何时在项目中使用(含示例),以及最佳实践。
· 9分钟 阅读

使用 AI 探索

在面向对象编程(OOP)这种软件设计方法中,我们使用“对象”来建模现实世界的实体。对象是一种包含属性(数据)和作用于这些数据的方法(函数)的数据结构。使用对象可以让复杂系统更易于设计。

想系统学习 OOP 的基础与软件工程理念,欢迎从我们的 Python 面向对象编程课程开始。

封装是 OOP 的一个基本概念。它意味着将对象的数据与方法组合在一起,并对外部隐藏实现细节。可以把封装理解为创建一个具有自身数据及其操作方式的“胶囊”(对象)的过程。

封装的主要目的在于:

  • 阻止对数据的直接访问(数据完整性)。
  • 仅暴露必要信息以简化系统(抽象)。

私有方法通过隐藏类的内部实现细节,是实现封装的关键。

将方法设为私有,表示该方法并不打算被直接访问。这样可以确保对象的内部状态只能以受控方式更改,从而避免错误与缺陷。

本文将介绍私有方法、其定义语法、在真实场景中的实现方式,以及使用私有方法的最佳实践。

什么是私有方法?

在面向对象编程(OOP)中,私有方法是类中仅供内部使用的函数。它们封装类的功能,确保内部运作不被外部直接干预。

这种封装提供了一层抽象,使类能够控制其内部状态与行为,并可在不影响外部依赖代码的前提下更改内部实现。

在 Python 中,约定通过在方法名前添加单下划线(_)或双下划线(__)来表示私有方法。这是一种向开发者提示方法预期作用域的惯例,仅为约定。

尽管该约定不像某些语言那样强制隐私,但在 Python 社区中被广泛遵循,用于表明方法的预期用法。稍后我们将更全面地探讨这些方面。

私有方法的语法

定义私有方法有两种方式,我们将通过示例来说明。

可以在函数名前加单下划线来定义私有方法:

class MyClass:
   def _private_method(self):
       print("This is a private method")

这种定义仅是一种对程序员的约定,因为该函数在类外仍然可以访问,但并不推荐这样做。

如果这是您第一次接触 Python 中的类,建议先阅读我们的 初学者友好教程:类(Classes)。

让我们创建一个对象并尝试访问刚创建的私有方法:

# Creating an object
obj = MyClass()

# Accessing from outside
obj._private_method()  # Not recommended to call directly, but possible

在 Python 中还可以使用双下划线来定义私有方法,语法如下:

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

obj = MyClass()
# obj.__private_method()  # This will raise an AttributeError

当我们尝试在类外访问该私有方法时,会抛出 AttributeError,表示不能直接通过函数名访问。

总结一下 Python 中私有方法的语法:

  • 单下划线:仅是程序员之间的约定,方法在类外仍可访问。
  • 双下划线:不能仅用函数名在类外直接访问。仍可通过“名称改写(Name Mangling)”访问,我们将在后文介绍。

在 Python 中实现私有方法

以真实场景为例,考虑一个安全且易用的 BankAccount 类,用于管理用户的金融交易。其需求包括:

  • 该类必须封装账户持有人的余额,允许存取款,同时确保余额不能在类外被直接操作。
  • 该类应支持账户间的安全转账,在不暴露内部余额修改机制的情况下更新收款账户的余额。
  • 该方案必须遵循面向对象编程原则,尤其是封装与抽象,以维护账户数据的完整性,并提供清晰、简洁的交互接口。

当然,银行账户还需要更多功能,如余额查询、货币转换、利息处理等——但为简化起见,我们在实现中暂不考虑这些。

让我们动手实现并理解这段代码。

class BankAccount:
   def __init__(self, account_holder, initial_balance=0):
       self.account_holder = account_holder
       self.__balance = initial_balance  # Private attribute

   def deposit(self, amount):
       if amount > 0:
           self.__balance += amount
           print(f"Deposited ${amount}. New balance: ${self.__balance}")
       else:
           print("Deposit amount must be positive.")

   def withdraw(self, amount):
       if 0 < amount <= self.__balance:
           self.__balance -= amount
           print(f"Withdrew ${amount}. New balance: ${self.__balance}")
       else:
           print("Insufficient funds or invalid amount.")

   def __update_balance(self, amount):  # Private method
       self.__balance += amount

   def transfer(self, amount, other_account):
       if 0 < amount <= self.__balance:
           self.withdraw(amount)
           other_account.__update_balance(amount)
           print(f"Transferred ${amount} to {other_account.account_holder}.")
       else:
           print("Insufficient funds or invalid amount.")

我们首先创建了一个 BankAccountclass,它封装了 BankAccount 对象所需的所有预期行为与功能。

BankAccount 类包含一个私有属性 __balance 和一个私有方法 __update_balance:

  • 私有变量(__balance):这是 BankAccount 类的属性,用于存储当前账户余额。通过在名称前添加两个下划线(__)将其标记为私有,表示仅打算在类内部访问。例如,它会在 deposit、withdraw 和 __update_balance 方法中被修改。不建议从外部访问该属性。
  • 私有方法(__update_balance):用于更新账户余额。该方法由 transfer 在转账操作中调用,以更新收款账户的余额。使用私有方法有助于封装更新余额的逻辑,确保只能通过类中定义的受控方式完成。

您可以新建一个 Python 文件,将以上与下方代码复制粘贴并运行,亲自查看效果。

# Using the Bank Account Solution
account1 = BankAccount("John Doe", 1000)
account2 = BankAccount("Jane Doe", 500)

account1.deposit(200)
account1.withdraw(100)
account1.transfer(300, account2)

您将看到如下输出:

Deposited $200. New balance: $1200
Withdrew $100. New balance: $1100
Withdrew $300. New balance: $800
Transferred $300 to Jane Doe.

恭喜!您已经使用私有方法实现了自己的 BankAccount 解决方案!

从输出可见,在执行存款、取款、转账等常规银行业务时,账户遵循了合理的业务逻辑。

__init__ 方法:它是私有方法吗?

您可能注意到示例中使用了 __init__ 方法。由于它也使用了双下划线,您或许会疑惑它是否也是私有方法。

__init__ 方法并不被视为为隐私目的而加双下划线的那类私有方法。

相反,它属于 Python 的特殊方法,通常称为 “dunder” 方法(“double underscore”的缩写),在语言中具有特定用途。其他此类特殊方法的示例包括 __str__、__repr__ 和 __del__。

让我们看看该特殊方法在示例中的用法:

class BankAccount:
   def __init__(self, account_holder, initial_balance=0):
       self.account_holder = account_holder
       self.__balance = initial_balance

如上所示,__init__ 方法是类的构造函数。它会在创建类的新实例时自动调用,用于初始化对象的属性。对 BankAccount 类而言,__init__ 用于设置 account_holder 和 __balance 的初始值。

进阶概念:Python 中的名称改写(Name Mangling)

前文提到,Python 中的私有方法更多是一种约定,Python 语言不像其他编程语言那样严格强制隐私。

因此我们看到,使用单下划线时可以直接访问所谓的私有方法,而使用双下划线时访问会受限。

为什么会这样?这源于 Python 中的一个概念——“名称改写(name mangling)”。

当您在属性名前加上双下划线时,Python 会自动修改该名称:在原属性名前加上单下划线和类名。

类在内部以这种修改后的名称存储该属性。这一修改名称的过程被称为名称改写。

名称改写有两个好处:

  • 防止命名冲突:在继承中,如果子类定义了与父类同名的属性,会产生冲突。名称改写通过确保名称对每个类唯一来避免此问题。
  • 提供一定程度的私有性:尽管 Python 不存在真正意义上的私有属性,名称改写可以让属性在类外部更难被访问,从而提供一定程度的私有性。

让我们通过示例来加深理解。

class MyClass:
   def __init__(self):
       self.__private_attribute = "I am private"

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

obj = MyClass()
print(obj.__private_attribute)  # This will raise an AttributeError

在此示例中,__private_attribute 和 __private_method 都经过了名称改写。在内部,它们分别存储为 _MyClass__private_attribute 和 _MyClass__private_method。尝试在类外使用原名称访问它们将导致 AttributeError。

不过,您仍可通过改写后的名称来访问它们:

print(obj._MyClass__private_attribute)  # This will print "I am private"
obj._MyClass__private_method()  # This will print "This is a private method"

需要注意的是,名称改写只是让属性更难被访问,并非万无一失的隐私保证。它更多是一种约定,表明该属性仅用于类的内部实现。

使用私有方法与变量的最佳实践

要有效利用私有方法和私有变量,需要遵循一些最佳实践。这些做法有助于确保代码不仅可用,还清晰、可维护且更安全。

建议遵循以下实践:

  • 对“受保护”的属性使用单下划线。如果希望表示某个属性或方法仅供类及其子类内部使用,而不面向外部,请在其名前加单下划线(_)。这是 Python 中的一种约定,用于向其他开发者传达该成员是“受保护”的,不应直接访问。
  • 对“私有”的属性使用双下划线。如果希望让某个属性或方法在类外更难被访问,以避免意外修改或误用,请在其名前加双下划线(__)。这会触发名称改写,使得从类外部访问它更困难(但并非不可能)。
  • 将私有方法用于内部逻辑。带单或双下划线前缀的私有方法适合封装类的内部实现逻辑。将它们用于与类实现细节密切相关且不应作为公共接口暴露的任务。

最后,坚持一致的编码最佳实践非常重要。遵循命名约定并在整个代码库中保持一致,这将使您的代码更易于他人(以及未来的您)理解和维护。

结语

本教程介绍了私有方法背后的概念、其语法以及适用场景。我们还通过多个示例展示了您如何在下一个编码项目中使用它们。最后,我们总结了使用私有方法时需要牢记的一些最佳实践。

如需进一步学习,您可以查看 Python Programming 技能路径 以及 Python 面向对象编程课程。

Arunn Thevapalan

作者

Arunn Thevapalan

作为一名高级数据科学家,我设计、开发并部署大规模机器学习解决方案,帮助企业做出更明智的数据驱动决策。作为数据科学写作者,我分享所学心得、职业建议以及深入的实践型教程。

主题