Python类名中的前导下划线

时间:2013-08-30 19:41:43

标签: python api class

这个问题建立在this one asked earlierthis one的基础之上,但是它解决了一个更微妙的问题,特别是什么算作“内部阶级”?

这是我的情况。我正在构建一个用于操作MS Office文件的Python库,其中许多类不是要在外部构造的,但是他们的许多方法都是API的重要部分。例如:

class _Slide(object):
    def add_shape(self):
        ...

_Slide不能在外部构建,作为库的最终用户,您可以通过调用Presentation.add_slide()获得新幻灯片。但是,一旦你有幻灯片,你肯定希望能够拨打add_shape()。所以该方法是API的一部分,但构造函数不是。这种情况在库中出现了几十次,因为只有Presentation类有一个外部/ API构造函数。

PEP8在这一点上含糊不清,引用了“内部类”而没有详细说明什么算作内部类。在这种情况下,我想可以说这个班级是“部分内部的”。

问题是我只有两个符号来区分至少三种不同的情况:

  1. 该类的构造函数和“public / API”方法都可以使用。
  2. 不使用构造函数,但类的“public / API”方法是。
  3. 该类确实是内部的,没有公共API,我保留在将来的版本中更改或删除它的权利。
  4. 我注意到从通信角度来看,外部API(库最终用户受众)的清晰表达与内部API(开发人员受众)之间存在冲突。对于最终用户,调用_Slide()是禁止/自担风险。但是,它是内部API的快乐成员,与_random_helper_method()不同,_Slide()只能在from module import *内调用。

    你对这个问题有什么看法可能对我有帮助吗?惯例是否规定我在类名上使用我的“单引导下划线”弹药来争取最终用户API的清晰度,或者我是否能够保留它以便与我和其他开发人员沟通关于何时真正使用类API私有而不被使用,例如,它所在的模块之外的对象,因为它是一个可能会改变的实现细节?


    更新:经过几年的进一步反思,我已经确定了在命名不打算作为一个类作为一个类的类时使用一个前导下划线的约定在他们的模块(文件)之外。这种访问通常用于实例化该类的对象或访问类方法等。

    这为模块的用户(当然通常是你自己:)提供了基本指标:“如果导入语句中出现带有前导下划线的类名,那么你做错了。” (单元测试模块是此规则的一个例外,此类导入可能经常出现在“内部”类的单元测试中。)

    请注意,这是对的访问。在模块外部访问该类(类型)的对象,可能由工厂或其他人提供,这是非常好的,也许是预期的。我认为将类与从它们创建的对象区分开来的失败导致了我最初的困惑。

    此约定还具有在制作{{1}}语句时不包括这些类的附带好处。虽然我从不在自己的代码中使用这些并建议避免使用它们,但这是一种合适的行为,因为这些类标识符并不是模块接口的一部分。

    经过多年的审判,这是我个人的“最佳实践”,当然不是“正确”的方式。您的里程可能会有所不同。

4 个答案:

答案 0 :(得分:4)

我认为马蒂诺的答案很好,当然最简单,最无耻。

但这绝不是唯一的选择。

经常使用的技术是将公共方法定义为接口类型的一部分;例如,zope.interface.Interface在扭曲的框架中被广泛使用。现代python可能会使用abc.ABCMeta来获得相同的效果。从本质上讲,公共方法Presentation.add_slide被记录为返回AbstractSlide的一些实例,它有更多的公共方法;但由于不可能直接构造AbstractSlide的实例,因此没有公开的方法来创建一个。

该技术可以特别方便,因为可以创建抽象类型的模拟实例,可以断言只调用公共方法;对于单元测试非常有用(特别是对于库的用户,以便他们可以确保他们只使用公共接口)。

另一种选择;因为创建幻灯片类实例的唯一公开方式是Presentation.add_slide;你可以让字面上是构造函数。这可能需要一些类型的神龛,这样的事情应该这样做。

from functools import partial

class InstanceConstructor(type):
    def __get__(cls, instance, owner):
        if instance is not None:
            return partial(cls, instance)
        return cls

只需定义班级add_slide中的__init__行为:

>>> class Presentation(object):
...     def __init__(self):
...         self.slides = []
...
...     class add_slide(object):
...         __metaclass__ = InstanceConstructor
...
...         def __init__(slide, presentation, color):
...             slide.color = color
...             presentation.slides.append(slide)
...
>>> p = Presentation()
>>> p.add_slide('red')
<instance_ctor.add_slide object at ...>
>>> p.slides
[<instance_ctor.add_slide object at ...>]
>>> p.slides[0].color
'red'

答案 1 :(得分:3)

我无法从您问题中的说明中分辨出来,但根据您在评论中提供的其他信息,我认为您的Slide课程实际上是公开的。

尽管事实只会通过调用add_slide()的{​​{1}}方法间接创建实例,但事实是这样,因为调用者将会自由(并且更有可能需要)调用实例的方法之后操纵它。在我看来,一个真正的私有类只能通过“拥有”它的类的方法来访问

让事情以任何其他方式破坏封装并增加设计组件之间的耦合,这两者都是不合需要的,应该尽可能地避免灵活性和可重用性。

答案 2 :(得分:1)

什么是“内部”类是一个常规问题,这就是为什么PEP没有定义术语,但是如果你将这些术语暴露给你的用户(提供返回这个类的方法,或者依赖于这个班级传来了)它根本不是“内部”。

你有三种选择。首先可能是记录并明确表达如何构造此类的意图,换句话说,允许用户构造和使用类的实例,并记录如何执行此操作。由于您希望更高级别的类跟踪这些Slide对象,因此请将构造函数接口强制设置并设置,以便模块的用户可以像您一样制作这些对象。

另一种方法是将您要在创建和管理这些实例的类中公开的此类方面包装起来,换句话说,使其成为真正的内部类。如果您只需要公开一个方法(比如add_shape()),这可能是非常合理的。但是,如果你想要暴露这个类的大块接口,那就会显得笨拙。

最后,您可以清楚地记录用户不应自己创建此类的实例,而是可以访问界面的某些部分。这很令人困惑,但可能是一种选择。

我喜欢第一个选择,我自己:将类暴露给模块的用户并使用其构造函数和析构函数实现,以确保它始终以正确的方式连接到需要了解它的其他类。然后,用户可以做一些事情,比如子类化这个东西并包装他们想要调用的方法,并确保你的对象保持连接。

答案 3 :(得分:0)

模块中的

__all__ = ['Presentation']隐藏了除{Presentation}之外的help()等函数的所有类。但是开发人员仍然可以从外部访问调用构造函数和方法。