您见过对象名称中带有下划线吗?您是否想知道这些下划线的含义以及为什么我们需要它们?在本文中,我们将深入研究 Python 中的面向对象编程,并尝试理解对象名称前的单下划线和双下划线的含义。
什么是OOP(面向对象编程)?
面向对象编程是一种编写代码的方式。我们的重点是创建由称为数据成员的属性和操作这些属性的函数组成的类。OOP 具有多态性、继承、封装和抽象等属性。
相关:了解有关Python 中的面向对象编程的更多信息。
让我们讨论面向对象编程的所有这些属性,以便更好地理解它。
封装
在 OOP 中,封装是通过将数据成员和成员函数绑定到存储在单个内存块中的类中来实现的。成员函数可以访问类的数据成员,同时对类的非成员函数隐藏数据成员。
相关:了解有关Python 封装的更多信息。
多态性
多态是一个接口、多个方法。它指的是面向对象编程为单个接口提供多种功能的能力。它提倡面向对象编程的DRY(不要重复自己)原则。多态性可以通过运算符重载或函数重载来实现。我们在进行运算符或函数重载时必须小心,因为它可能会导致歧义。
遗产
继承是通过继承类来获取先前构建的类的属性和功能的过程。继承另一个类的类称为子类,被继承的类称为父类或基类。我们可以在同一个类中继承多个类,获取所有继承类的属性和功能。继承还促进了 OOP 的 DRY 原则。继承也可能导致歧义。我们将在本文后面详细讨论由于继承而导致的歧义。
相关:了解有关Python 中的继承的更多信息。
抽象
OOP 中的抽象是隐藏属性或限制对类的数据成员或成员函数的访问的能力。它可以通过使用私有和公共等访问说明符来实现。public 访问说明符允许整个程序访问某个成员,而 private 只允许类的成员函数访问使用该访问说明符的成员。
Python中如何实现抽象?
如果您使用过 C++ 或 Java,它们具有 public 和 private 等访问说明符,可以在 OOP 中使用它们来实现抽象。Python 没有任何访问说明符。这背后的原因是Python的目标是拥有尽可能简单的语法,以便程序员可以专注于其他事情,而不是浪费时间编写大语法,这可能会导致混乱并增加错误。Python 的发明者 Guido Van Rossum 曾经说过(我引用一下):“丰富的语法带来的负担多于帮助”。
访问说明符增加了代码的复杂性,并且对有效执行 OOP 没有多大帮助。因此,Python中默认所有数据成员和成员函数都是公开的。现在你一定在想,“等等,这是否意味着我们不能在 Python 中执行抽象?这只是对 OOP 的不尊重。作为一种无法执行抽象的 OOP 语言有什么意义呢?”
不用担心。Python 不会让你失望。Python 有一种不同的方式来实现抽象。Python 在成员名称前使用下划线来实现抽象。当您声明成员时,在成员名称前添加双下划线将使其无法从类中访问。这听起来可能很复杂,但请继续关注我们;你会很容易理解的。
使用前导单下划线和双下划线对成员进行私有化
前导单下划线
1
2
3
4
5
|
class sample_class: def accessible( self ): print ( "You can access me out of the class' scope." ) def _inaccessible( self ): print ( "Don't access me out of the class' scope." ) |
在上面的代码块中,我们在无法访问的函数之前使用了下划线。这背后的原因并不是 Python 的任何功能。它只是 Python 用户用来指定类的私有成员的术语。
前导双下划线
1
2
3
4
5
|
class sample_class: def accessible( self ): print ( "You can access me out of the class' scope." ) def __inaccessible( self ): print ( "You cannot access me out of the class' scope :(" ) |
在上面的代码块中,我们首先声明了一个名为“sample_class”的类。然后我们在其中声明了两个函数。
首先是可访问的,当我们调用该函数时,它将打印“您可以在类范围之外访问我”。该函数可以在类范围之外访问,因为它是正常声明的,并且所有类成员在 Python 中默认都是公共的。
第二个函数是 __inaccessible,当我们调用它时,它会打印:“你不能在类的范围之外访问我。” 该函数无法在类范围之外访问,因为我们使用下划线开头该函数的名称。
现在我们来测试一下函数私有化是否有效。
首先,我们将创建示例 _class 的对象。
obj = sample_class() |
现在,我们将尝试访问 obj 的可访问函数。
obj.accessible() |
伟大的!当我们尝试从类作用域访问可访问函数时,它会打印“您可以在类作用域外访问我”。现在让我们尝试从类中访问 __inaccessible 函数。
obj.__inaccessible() |
它抛出属性错误。这意味着我们做到了。我们无法从类之外访问 __inaccessible 函数。
使用前导单下划线访问类的私有成员
当您正确检查上述输出时,它会显示“AttributeError:’sample_class’对象没有属性’__inaccessible’”。为什么?
这种方法并非万无一失。Python 所做的不是将函数私有化,而是将其名称更改为 _<classname>__<functionname>。
因此,在我们的例子中,__inaccessible 函数的名称更改为 _sample_class__inaccessible。现在,让我们尝试用这个名称来访问它。
obj._sample_class__inaccessible() |
因此,当我们尝试使用 _sample_class__inaccessible() 访问 __inaccessible 函数时,我们会得到输出“您无法在类范围之外访问我:(”。这意味着我们可以使用时间名称来访问它。
这样我们就明白为什么在对象名称前使用一个下划线了。现在让我们尝试理解为什么在对象名称之前使用双下划线。
使用前导双下划线进行名称修改
在研究继承时,我们了解到继承有时可能会导致歧义。当我们声明具有相同名称的父类和基类成员时,可能会出现这种情况。
让我们尝试通过一个例子来理解。
1
2
3
4
5
6
|
class Parent: def member( self ): print ( "I am a member of Parent class." ) class Child(Parent): def member( self ): print ( "I am a member of Child class." ) |
在上面的代码块中,我们创建了一个名为 Parent 的类并声明了一个成员函数。我们还声明了一个子类,并在其中声明了一个同名的成员函数。
child_obj = Child() |
创建 Child 类的实例并将其命名为 child_obj。现在让我们尝试从这个子类对象访问成员函数。
child_obj.member() |
因此,当我们尝试从子对象中访问与继承的Parent类中的成员函数同名的成员函数时,解释器会调用子类的成员函数。
我们如何访问继承父类的成员函数呢?
我们可以通过在类中的成员声明之前使用双下划线来做到这一点。
1
2
3
4
5
6
|
class Parent: def __member( self ): print ( "I am a member of Parent class." ) class Child(Parent): def __member( self ): print ( "I am a member of Child class." ) |
就像我们在私有化中所做的那样,在变量名称更改名称之前使用 __ 或双下划线声明变量名称 _<classname>__<functionname>。因此,由于这一点,我们可以访问父类的成员函数,该函数与子类的成员函数同名。
1
2
3
|
child_obj = Child() child_obj._Parent__member() child_obj._Child__member() |
尾随单下划线
Python 中的单个尾随下划线是一种传统命名法,用于避免 Python 中的名称冲突。当我们经常想用特定的单词来命名变量或函数时,就会出现名称冲突,因为已经有一个同名的内置函数。
例如,您不能命名列表 – list,因为列表是内置对象的名称。在这种情况下,我们可以将列表命名为“list_”。这是 Python 中非常常见的术语。我们使用这个命名法将类命名为“class_”或将对象命名为“object_”。这是一个很好的做法,因为我们不会浪费时间考虑变量的名称。
尾随双下划线
在 Python 中没有试验双下划线的特定用例。在 Python 中,单独的尾随双下划线是没有意义的。但与前导下划线配合使用,它具有很多功能。让我们来讨论一下它们。
双下划线的更多用例
变量名前后的双下划线象征着魔术方法。魔法方法也称为dunder方法。它们在 Python 中非常常见并且非常有用。我们不必定义魔术方法,它们是由解释器自动定义的。让我们尝试更多地了解魔法方法。
要检查变量的 dunder 方法,请使用 dir 函数。让我们检查一下字符串的 dunder 方法。
print ( dir ( str )) |
因此,当我们对字符串调用 dir 函数时,我们得到了字符串数据结构的所有函数。所有类型的函数__<function_name>__
都是 magic 或 dunder 函数。这些不能直接调用。例如, __add__ 方法指定在字符串上使用“+”运算符时会发生什么。__mul__ 用于乘法,__len__ 用于 len 函数等。
最重要的魔术方法是 __init__ 方法。它是类的构造函数。类必须具有 __init__ 方法来初始化所有类数据成员。
如果需要,可以修改魔法方法。例如,如果您希望“+”运算符有不同的行为,您可以根据需要修改 __add__ 方法。魔法方法非常重要。确保您对它们有正确的理解。
结论
因此,我们学习了两种在对象名称前使用下划线的方法。第一个是指定成员的可访问性,第二个是调用父类的成员函数,该成员函数与子类的成员函数同名。乍一看,它可能看起来很难理解,但当你深入挖掘时,你就会知道它非常简单但很重要。
参考
堆栈溢出对同一问题的回答。