在 Python 中的非库代码中是否应该使用下划线 _ 作为 "access modifier indicator"?

Should you use the underscore _ as an "access modifier indicator" in non-library code in Python?

简介


所以我一直在对下划线字符 (_) 进行一些研究。我知道它的大部分用例及其语义,所以我将它们放在下面作为回顾,我将以这个问题作为结尾,这个问题更多的是关于两个用例的概念性问题。

用例


  1. 将最后的计算存储在解释器中(在解释器之外 has no special semantics 并且未定义为单个字符)
  2. In internationalization context,您可以在其中导入 gettext 等别名为 _
  3. 的函数
  4. 十进制分组以提高可见性(特别是 3 组,例如 1_000_000)- 请注意,这仅适用于 Python 3.6 以后.

    示例:

    1_000_000 == 10**6  # equals True
    x = 1_000
    print(x)  # yields 1000
    
  5. 到 "ignore" 某些值,尽管我不会称它为 "ignore" 因为这些值仍然被评估并绑定到 _ 就像它是一个常规标识符。通常我会找到比这更好的设计,因为我发现这是 code smell。多年来我很少使用这种方法,所以我想只要您认为需要使用它,您肯定可以改进您的设计以不使用它。

    示例:

    for _ in range(10):
        # do stuff without _
        ping_a_server()
    
    # outside loop that still exists and it still has the last evaluated value
    print(_)  # would yield 9
    
  6. 尾随标识符(used by convention 以避免名称与内置标识符或保留字冲突):

    例子

    class Automobile:
    
        def __init__(self, type_='luxury', class_='executive'):
            self.car_type = type_
            self.car_class = class_
    
    noob_car = Automobile(type_='regular', class_='supermini')
    luxury = Automobile()
    
  7. 作为真正意义上的访问修饰符but only as a convetion since Python doesn't have access modifiers

    单前导下划线


    Acts as a weak "internal use" indicator。所有以 _ 开头的标识符将被 star imports (from M import *)

    忽略

    示例:

    a.py
    
    _bogus = "I'm a bogus variable"
    __bogus = "I'm a bogus with 2 underscores"
    ___triple = 3.1415
    class_ = 'Plants'
    type_ = 'Red'
    regular_identifier = (x for x in range(10))
    
    b.py
    from a import *
    print(locals())  # will yield all but the ones starting with _
    

    重要的概念观察

    我讨厌人们称其为私有(不包括 在 Python) 中实际上没有什么是私有的。 如果我们把它放在一个类比中,这将等同于 Java's protected,因为在 Java protected 中意味着“derived classes and/or in same package ”。因此,由于在模块级别,任何带有前导下划线 _ 的标识符都与常规标识符具有不同的语义(我是从 Python 的角度而不是我们的角度谈论语义 CONSTANTSglobal_variable 意味着不同的东西,但对于 Python 它们是同一件事)并且在谈论开始导入时被导入机器忽略,这确实是一个指标,表明您应该在其中使用这些标识符模块或在 classes 中定义它们或其派生子 classes.

    双前导下划线


    无需赘述,这将调用名称修改机制 当用于 class 内的标识符时,这会变得更难,但是 同样,人们并非不可能从 classes 访问属性 subclass 即基础 class.

问题


所以我一直在阅读 this book 献给初学者的书,在变量部分,作者说了类似的话:

Variables can begin with an underscore _ although we generally avoid doing this unless we write library code for others to use.

这让我开始思考...在私有项目中,甚至在未用作依赖项的开源项目中,将内容标记为非 public 是否有意义其他项目?

例如,我有一个 open source web app,我会定期推送更改。它主要用于教育目的,因为我想编写干净、标准化的代码并将我在此过程中获得的任何新技能付诸实践。现在我在想前面提到的:Does it mean to use identifiers that mark things non-public?

为了争论起见,让我们假设将来有 500 人正在积极地为该 Web 应用程序做出贡献,并且它在代码方面变得非常活跃。我们可以假设很多人会直接使用这些 "protected" 和 "private" 标识符(即使不建议这样做,也不是所有 500 人都知道最佳实践)但是 因为它是一个非库项目,这意味着它不是其他项目的依赖项或其他人使用的,他们可以 "somewhat" 在某种意义上放心,这些方法不会在代码重构中消失,因为进行重构的开发人员可能会注意到项目中的所有调用者,并会相应地进行重构(或者他不会注意到,但测试会告诉他)。

显然这在图书馆代码中是有意义的,因为所有依赖于你的图书馆的人以及所有可能依赖于你的图书馆的未来人或间接依赖于你的图书馆的人(其他人将你的图书馆嵌入他们的和暴露他们的图书馆等)应该知道有一个单尾随下划线双尾随下划线的标识符是一个实现细节,可以在随时。因此,他们应该始终使用您的 public API。

如果没有人参与这个项目,我会把它设为私有,我将是唯一一个参与其中的人怎么办?或者一小部分人。 在这类项目中使用访问修饰符指示符是否有意义?

您的观点似乎是基于这样的假设,即什么是私有的或 public(或 python 中的等效建议)是基于读写代码的开发人员。这是错误的。

即使你单独编写一个你只会使用的应用程序,如果设计正确,它也会被分成模块,这些模块将公开一个接口并有一个私有实现。

您同时编写模块和使用它的代码的事实并不意味着没有应该私有的部分来保持封装。所以,是的,无论有多少开发人员从事该项目或依赖于该项目,使用前导下划线将模块的一部分标记为私有都是有意义的。

编辑:

我们真正讨论的是 encapsulation,这是 python 和任何其他语言的软件工程通用的概念。

这个想法是将整个应用程序分成几部分(我正在谈论的模块,可能是 python 包,但也可能是您设计中的其他东西)并决定几个功能中的哪一个执行您的目标所需的是在那里实现的(这称为 Single Responsibility Principle)。

这是 design by contract 的好方法,这意味着决定您的模块将向软件的其他部分公开的抽象,并将不属于它的所有内容隐藏为实现细节。其他模块不应该仅依赖于您公开的功能来实现您的实现,这样您就可以在想要提高性能、支持新功能、提高可维护性或任何其他原因时自由更改。

现在,所有这些理论上的争论都与语言和应用程序无关,这意味着每次您设计一个软件时,您都必须依赖该语言提供的工具来设计您的模块和构建封装。

Python,据我所知,即使不是唯一的,也是为数不多的人之一,做出了深思熟虑的选择(在我看来是错误的),不强制封装,但允许开发人员拥有正如您已经发现的那样,可以访问所有内容。

然而,这并不意味着上述概念不适用,只是它们不能在语言级别强制执行,必须以更宽松的方式实现,作为一个简单的建议。

这是否意味着封装实现并自由使用每一位可用信息是个坏主意?显然不是,它仍然是构建架构的一个很好的 SOLID 原则。

所有这些都不是真正必要的,它们只是经过时间和经验证明可以创建高质量软件的好原则。

你应该在你的小应用程序中使用没有其他人使用它吗?如果你希望事情按他们应该的方式完成,是的,你应该。

它们是必要的吗?好吧,你可以在没有的情况下让它工作,但你可能会发现它会在以后花费更多的精力。

如果我不是在编写一个库而是一个已完成的应用程序怎么办?那么,这是否意味着您不应该以良好、干净、整洁的方式编写它?