对象名称前的单下划线和双下划线是什么意思?

2024-11-18 08:40:00
admin
原创
15
摘要:问题描述:在 Python 中,对象名称前的单下划线和双下划线代表什么?解决方案 1:单下划线在类中,以下划线开头的名称向其他程序员表明该属性或方法旨在在该类内部使用。但是,隐私不会以任何方式强制执行。在模块中对函数使用前导下划线表示不应从其他地方导入该函数。来自PEP-8风格指南:_single_leadi...

问题描述:

在 Python 中,对象名称前的单下划线和双下划线代表什么?


解决方案 1:

单下划线

在类中,以下划线开头的名称向其他程序员表明该属性或方法旨在在该类内部使用。但是,隐私不会以任何方式强制执行。在模块中对函数使用前导下划线表示不应从其他地方导入该函数。

来自PEP-8风格指南:

_single_leading_underscore:弱“内部使用”指示符。例如,from M import *不导入名称以下划线开头的对象。

双下划线(名称混乱)

来自Python 文档:

任何形式为 的标识符__spam(至少两个前导下划线,最多一个尾随下划线)在文本上均被替换为_classname__spam,其中classname是当前类名,前导下划线被删除。此处理与标识符的语法位置无关,因此可用于定义类私有实例和类变量、方法、存储在全局变量中的变量,甚至存储在实例中的变量。在其他类的实例上对此类是私有的。

同一页面上还有一条警告:

名称改编旨在为类提供一种定义“私有”实例变量和方法的简单方法,而不必担心派生类定义的实例变量,也不必担心类外的代码对实例变量进行修改。请注意,改编规则主要是为了避免意外;但有决心的人仍然有可能访问或修改被视为私有的变量。

例子

>>> class MyClass():
...     def __init__(self):
...             self.__superprivate = "Hello"
...             self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

解决方案 2:

  • _foo:仅是一种约定俗成。程序员用来表明变量是私有变量(无论在 Python 中是什么意思)的一种方式。

  • __foo:这具有实际意义。解释器用 替换此名称,_classname__foo以确保该名称不会与另一个类中的类似名称重叠。

  • __foo__:仅是一种约定。一种让 Python 系统使用不会与用户名冲突的名称的方法。

在 Python 世界中,其他形式的下划线都没有任何意义。此外,在这些约定中,类、变量、全局等之间没有区别。

解决方案 3:

到目前为止,答案都很棒,但缺少一些细节。单个前导下划线不仅仅是一种惯例:如果您使用from foobar import *,并且模块foobar未定义__all__列表,则从模块导入的名称包括带有前导下划线的名称。我们假设它主要是一种惯例,因为这种情况是一个非常模糊的角落;-)。

前导下划线约定不仅广泛用于私有名称,也用于 C++ 所称的受保护名称 - 例如,完全旨在由子类覆盖的方法名称(甚至必须被覆盖,因为它们在基类中raise NotImplementedError!-)通常是单前导下划线名称,以向使用该类(或子类)实例的代码指示所述方法不应直接调用。

例如,为了创建一个具有不同于 FIFO 的排队规则的线程安全队列,需要导入 Queue、子类化 Queue.Queue,并覆盖诸如 和_get之类的方法_put;“客户端代码”从不调用那些(“钩子”)方法,而是调用(“组织”)公共方法,例如putget(这被称为模板方法设计模式 - 例如,请参见此处,了解基于我关于该主题的演讲视频的有趣演示,并附加了演讲记录的摘要)。

编辑:演讲描述中的视频链接现已损坏。您可以在此处和此处找到前两个视频。

解决方案 4:

._variable是半私人的,仅用于会议

.__variable经常被错误地认为是超级私有的,而其实际含义只是为了防止意外访问而进行名称修改[1]

.__variable__通常为内置方法或变量保留

如果你迫切需要,你仍然可以访问.__mangled变量。双下划线只是将变量重命名为类似instance._className__mangled

例子:

class Test(object):
    def __init__(self):
        self.__a = 'a'
        self._b = 'b'

>>> t = Test()
>>> t._b
'b'

t._b 是可访问的,因为它只是按照惯例被隐藏

>>> t.__a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'

未找到 t.__a,因为名称改编导致它不再存在

>>> t._Test__a
'a'

通过访问instance._className__variable而不是仅仅访问双下划线名称,您可以访问隐藏的值

解决方案 5:

以单下划线开头:

Python 没有真正的私有方法。相反,方法或属性名称开头的一个下划线表示您不应访问此方法,因为它不是 API 的一部分。

class BaseForm(StrAndUnicode):
    
    def _get_errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

    errors = property(_get_errors)

(此代码片段取自 Django 源代码:django/forms/forms.py)。在此代码中,errors是一个公共属性,但此属性调用的方法 _get_errors 是“私有的”,因此您不应访问它。

开头的两个下划线:

这会引起很多混乱。它不应该用于创建私有方法。它应该用于避免您的方法被子类覆盖或意外访问。让我们看一个例子:

class A(object):
    def __test(self):
        print "I'm a test method in class A"

    def test(self):
        self.__test()
 
a = A()
a.test()
# a.__test() # This fails with an AttributeError
a._A__test() # Works! We can access the mangled name directly!

输出:

$ python test.py
I'm test method in class A
I'm test method in class A

现在创建一个子类 B,并对 __test 方法进行自定义

class B(A):
    def __test(self):
        print "I'm test method in class B"

b = B()
b.test()

输出将是...

$ python test.py
I'm test method in class A

正如我们所见,B.test() 没有调用 B.__test() 方法,正如我们所料。但事实上,这是 的正确行为。两个名为 __test() 的方法会自动重命名(混乱)为 _A__test() 和 _B__test(),因此它们不会被意外覆盖。当您创建以 开头的方法时,这意味着您不希望任何人能够覆盖它,并且您只打算从其自己的类内部访问它。

开头和结尾有两个下划线:

当我们看到类似这样的方法时__this__,不要调用它。这是 Python 应该调用的方法,而不是你。让我们来看看:

>>> name = "test string"
>>> name.__len__()
11
>>> len(name)
11

>>> number = 10
>>> number.__add__(40)
50
>>> number + 50
60

总会有一个运算符或本机函数调用这些魔法方法。有时它只是 Python 在特定情况下调用的一个钩子。例如,在调用来构建实例__init__()后创建对象时调用...__new__()

让我们举个例子...

class FalseCalculator(object):

    def __init__(self, number):
        self.number = number

    def __add__(self, number):
        return self.number - number

    def __sub__(self, number):
        return self.number + number

number = FalseCalculator(20)
print number + 10      # 10
print number - 20      # 40

更多细节,请参阅PEP-8 指南。更多魔法方法,请参阅此 PDF。

解决方案 6:

根据Python 中下划线的含义

  • 单前导下划线(_var:命名约定表示名称仅供内部使用。通常 Python 解释器不强制执行(通配符导入除外),仅作为对程序员的提示。

  • 单尾下划线(var_:按照惯例使用,以避免与 Python 关键字的命名冲突。

  • 双前导下划线(__var:在类上下文中使用时触发名称修改。由 Python 解释器强制执行。

  • 双前导和尾随下划线(__var__:表示 Python 语言定义的特殊方法。请避免对您自己的属性使用这种命名方案。

  • 单下划线(_:有时用作临时或不重要的变量的名称(“不关心”)。还有:Python REPL中最后一个表达式的结果。

解决方案 7:

由于很多人都在参考雷蒙德的演讲,我只想把他说的话写下来,让大家更容易理解:

双下划线的用意不是为了隐私。其用意正是如此

class Circle(object):

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        p = self.__perimeter()
        r = p / math.pi / 2.0
        return math.pi * r ** 2.0

    def perimeter(self):
        return 2.0 * math.pi * self.radius

    __perimeter = perimeter  # local reference


class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

它实际上与隐私相反,它完全关乎自由。它使你的子类可以自由地覆盖任何一个方法,而不会破坏其他方法

假设您没有perimeter在 中保留 的本地引用Circle。现在,派生类会Tire覆盖 的实现perimeter,而无需触及area。当您调用 时Tire(5).area(),理论上它仍应使用Circle.perimeter进行计算,但实际上它正在使用Tire.perimeter,这不是预期的行为。这就是为什么我们需要 Circle 中的本地引用。

但为什么__perimeter不用 呢_perimeter?因为_perimeter仍然给了派生类重写的机会:

class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

    _perimeter = perimeter

双下划线有名称混乱,因此父类中的本地引用在派生类中被覆盖的可能性很小。因此“使您的子类可以自由地覆盖任何一个方法而不会破坏其他方法”。

如果您的类不会被继承,或者方法覆盖不会破坏任何东西,那么您根本就不需要__double_leading_underscore

解决方案 8:

有时,你会看到一个带有下划线的元组,例如

def foo(bar):
    return _('my_' + bar)

在这种情况下,_() 是本地化函数的别名,该函数根据语言环境对文本进行操作,将其转换为适当的语言等。例如,Sphinx 就是这样做的,您会在导入中找到

from sphinx.locale import l_, _

在sphinx.locale中,_()被指定为某些本地化函数的别名。

解决方案 9:

  • _var:python 中以单下划线开头的变量是经典变量,用于告知使用您代码的其他人该变量应保留供内部使用。它们与经典变量有一点不同:在对定义它们的对象/模块进行通配符导入时不会导入它们(定义变量时除外__all__)。例如:

# foo.py

var = "var"
_var = "_var"
# bar.py

from foo import *

print(dir())  # list of defined objects, contains 'var' but not '_var'
print(var)    # var
print(_var)   # NameError: name '_var' is not defined
  • _:单下划线是前导单下划线变量的一个特例。它通常用作垃圾变量,用于存储以后不打算访问的值。它也不会通过通配符导入来导入。例如:此for循环打印“我不能在课堂上说话”10 次,并且永远不需要访问该_变量。

for _ in range(10):
    print("I must not talk in class")
  • var_:单尾下划线变量。它们是约定俗成的经典变量,可避免与 Python 关键字冲突。例如:

class_ = "MyClassName"
  • __var:双前下划线变量(至少两个前下划线,最多一个尾部下划线)。当用作类属性(变量和方法)时,这些变量易受名称改编:在类之外,python 会将属性重命名为_<Class_name>__<attribute_name>。示例:

class MyClass:
    __an_attribute = "attribute_value"

my_class = MyClass()
print(my_class._MyClass__an_attribute)  # "attribute_value"
print(my_class.__an_attribute)  # AttributeError: 'MyClass' object has no attribute '__an_attribute'

当用作类外的变量时,它们的行为类似于单前导下划线变量。

  • __var__:双下划线变量(至少两个前导和尾随下划线)。也称为dunders。python 使用此命名约定在内部定义变量。避免使用此约定以防止 python 更新时可能出现的名称冲突。双下划线变量的行为类似于单前导下划线变量:在类内部使用时不受名称修改的影响,但不会在通配符导入中导入。

解决方案 10:

如果真的想让变量只读,我认为最好的方法是使用 property(),只向其传递 getter。使用 property(),我们可以完全控制数据。

class PrivateVarC(object):

    def get_x(self):
        pass

    def set_x(self, val):
        pass

    rwvar = property(get_p, set_p)  

    ronly = property(get_p) 

我知道 OP 问了一个稍微不同的问题,但是因为我发现另一个问题询问“如何设置私有变量”与这个问题重复,所以我想在这里添加这个额外的信息。

解决方案 11:

很棒的答案,全部都是正确的。我提供了简单的例子以及简单的定义/含义。

意义:

some_variable --► 它是公开的,任何人都可以看到它。

_some_variable --► 它是公开的,任何人都可以看到它,但这是表示私有的惯例...警告Python 不会强制执行。

__some_varaible --► Python 用 _classname__some_varaible (又称名称混淆) 替换变量名,并降低/隐藏其可见性,使其更像是私有变量。

说实话根据 Python 文档

“Python 中不存在只能从对象内部访问的“私有”实例变量”

例如:

class A():
    here="abc"
    _here="_abc"
    __here="__abc"


aObject=A()
print(aObject.here) 
print(aObject._here)
# now if we try to print __here then it will fail because it's not public variable 
#print(aObject.__here)

解决方案 12:

这是一个简单的示例,说明双下划线属性如何影响继承的类。因此,使用以下设置:

class parent(object):
    __default = "parent"
    def __init__(self, name=None):
        self.default = name or self.__default

    @property
    def default(self):
        return self.__default

    @default.setter
    def default(self, value):
        self.__default = value


class child(parent):
    __default = "child"

如果你在 python REPL 中创建一个子实例,你会看到以下内容

child_a = child()
child_a.default            # 'parent'
child_a._child__default    # 'child'
child_a._parent__default   # 'parent'

child_b = child("orphan")
## this will show 
child_b.default            # 'orphan'
child_a._child__default    # 'child'
child_a._parent__default   # 'orphan'

对于某些人来说,这可能是显而易见的,但在更为复杂的环境中,这让我措手不及。

解决方案 13:

单下划线开头是一种惯例。从解释器的角度来看,名称是否以单下划线开头没有区别。

双前导和尾随下划线用于内置方法,例如__init____bool__等。

双前下划线无尾下划线也是一种惯例,但是,类方法将被解释器破坏。对于变量或基本函数名称,不存在任何差异。

解决方案 14:

你的问题很好,这不仅仅是关于方法。模块中的函数和对象通常也以一个下划线为前缀,也可以以两个下划线为前缀。

但是,例如,模块中的 __double_underscore 名称不会发生名称混乱。如果从模块 (from module import *) 导入所有名称,则不会导入以一个 (或多个) 下划线开头的名称,help(module) 中也不会显示这些名称。

解决方案 15:

Python 中不存在“私有”实例变量,即除了从对象内部之外无法访问的变量。但是,大多数 Python 代码都遵循一个惯例:带有下划线前缀的名称(例如 _spam)应被视为 API 的非公共部分(无论它是函数、方法还是数据成员)。它应被视为实现细节,如有更改,恕不另行通知。

参考
https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references

解决方案 16:

了解 和 _ 的事实相当容易;其他答案都表达得相当好。用法则更难确定。

我的看法是这样的:

_

应该用来表明某个函数不是供公众使用的,例如 API。这和导入限制使其行为internal与 c# 非常相似。

__

应该用来避免继承层次结构中的名称冲突并避免后期绑定。与 c# 中的 private 非常相似。

==>

如果你想表明某物不用于公共用途,但它应该表现得像protecteduse _。 如果你想表明某物不用于公共用途,但它应该表现得像privateuse __

这也是我很喜欢的一句话:

问题在于,类的作者可能会合理地认为“此属性/方法名称应为私有的,只能从此类定义内部访问”,并使用 __private 约定。但后来,该类的用户可能会创建一个合法需要访问该名称的子类。因此,要么必须修改超类(这可能很困难或不可能),要么子类代码必须使用手动修改的名称(这充其量是丑陋和脆弱的)。

但我认为问题在于,如果没有 IDE 在您覆盖方法时发出警告,如果您意外地覆盖了基类中的方法,那么查找错误可能需要一段时间。

解决方案 17:

对于方法,您可以使用双下划线来隐藏私有“方法”,模式如下:

# Private methods of MyClass
def _MyClass__do_something(obj:'MyClass'):
    print('_MyClass__do_something() called. type(obj) = {}'.format(type(obj)))

class MyClass():
    def __init__(self):
        __do_something(self)

mc = MyClass()

输出:

_MyClass__do_something() called. type(obj) = <class '__main__.MyClass'>

今天,当我尝试对类方法使用双下划线并出现NameError: name '_<class><method>' is not defined错误时,我偶然发现了这个问题。

解决方案 18:

简单来说,让我们将 Python 变量的可访问性约定与 Java 中的访问修饰符进行比较:

(Python)                                     =   (Java)
_single_underscore_variable                  =   Protected (Accessible to class and its subclasses)
__double_underscore_variable                 =   Private (Accessible to class itself only)
no_underscore_variable                       =   Public (Accessible anywhere)

参考https://www.tutorialsteacher.com/python/public-private-protected-modifiers

解决方案 19:

_obj 表示受保护(可以在定义类及其子类中访问)

--obj 表示私有(它们不能从定义它们的类的外部直接访问。但是,Python 并不像其他一些语言那样强制执行严格的私有访问;相反,它执行名称修改以使访问更加困难但并非不可能。)

Mangling 是 Python 中的一项功能,可修改类成员的名称,使其更难从类外部直接访问。这是一种用于创建“伪私有”变量和方法的技术。这是通过在成员名称前加上双下划线 (__) 来实现的,但不以另一个双下划线结尾

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用