【问题标题】:Is there a usecase for nested classes?嵌套类有用例吗?
【发布时间】:2011-05-27 20:13:23
【问题描述】:

我最近看到有几个人在 Stackoverflow 上做这样的事情:

class A:
    foo = 1

    class B:
        def blah(self):
            pass

换句话说,它们有嵌套类。这行得通(尽管 Python 新手似乎遇到了问题,因为它的行为不像他们想象的那样),但我想不出任何理由在任何语言中都这样做,当然在 Python 中也不行。有这样的用例吗?人们为什么要这样做?搜索这个似乎在 C++ 中相当普遍,那里有充分的理由吗?

【问题讨论】:

    标签: c++ python ruby oop


    【解决方案1】:

    在 Python 中,更有用的模式是在函数或方法中声明类。正如人们在其他答案中所指出的那样,在另一个类的主体中声明一个类几乎没有用 - 是的,它确实避免了对模块命名空间的污染,但是由于根本没有一个模块命名空间,它上面还有几个名字不打扰。即使额外的类不打算由模块的用户直接实例化,将 then 放在模块根目录上也可以让其他人更容易访问它们的文档。

    但是,函数内部的类是完全不同的生物:每次运行包含类主体的代码时,它都会被“声明”并创建。这使人们有可能以一种非常简单的方式为各种用途创建动态类。例如,以这种方式创建的每个类都位于不同的闭包中,并且可以访问包含函数上变量的不同实例。

    def call_count(func):
        class Counter(object):
           def __init__(self):
               self.counter = 0
           def __repr__(self):
               return str(func)
           def __call__(self, *args, **kw):
               self.counter += 1
               return func(*args, **kw)
        return Counter()
    

    并在控制台上使用它:

    >>> @call_count
    ... def noop(): pass
    ...
    >>> noop()
    >>> noop()
    >>> noop.counter
    2
    >>> noop
    <function noop at 0x7fc251b0b578>
    

    所以,一个简单的 call_counter 装饰器可以使用静态“Counter”类,在函数外部定义,并接收 func 作为其构造函数的参数 - 但如果你想调整其他行为,比如在这个例子中,制作 repr( func) 返回函数表示,而不是类表示,这样更容易制作。 .

    【讨论】:

      【解决方案2】:

      “嵌套类”可以表示两种不同的东西,可以按意图分为三个不同的类别。第一个是纯粹的风格,另外两个用于实际目的,并且高度依赖于使用它们的功能语言。

      1. 为了创建新的命名空间和/或更好地组织代码而使用嵌套类定义。例如,在 Java 中,这是通过使用 static 嵌套类来实现的,the official documentation 建议将其作为一种创建更具可读性和可维护性的代码并将类逻辑分组在一起的方法。然而,Python 之禅建议您少嵌套代码块,因此不鼓励这种做法。

        import this

        在 Python 中,您会经常看到按模块分组的类。

      2. 将一个类放入另一个类作为其接口(或实例的接口)的一部分。首先,实现可以使用此接口来辅助子类化,例如想象一个嵌套类HTML.Node,您可以在HTML 的子类中覆盖它以更改用于创建新节点实例的类。其次,这个接口可能会被类/实例用户使用,尽管除非你是下面描述的第三种情况,否则这不是很有用。

        至少在 Python 中,您不需要嵌套定义来实现其中任何一个,但这种情况可能非常少见。相反,您可能会在类外部看到 Node 定义,然后在类定义中看到 node_factory = Node(或专用于创建节点的方法)。

      3. 嵌套对象的命名空间,或为不同的对象组创建不同的上下文。在Java 中,非静态 嵌套类(称为inner 类)绑定到外部类的实例。这非常有用,因为它允许您拥有位于不同外部命名空间内的内部类的实例。

        对于 Python,请考虑 decimal 模块。您可以创建不同的上下文,并为每个上下文定义不同的精度。每个Decimal 对象可以在创建时分配一个上下文。这通过不同的机制实现了与内部类相同的效果。如果 Python 支持内部类,并且 ContextDecimal 是嵌套的,那么您将使用 context.Decimal('3') 而不是 Decimal('3', context=context)

        您可以轻松地在 Python 中创建一个元类,它允许您创建位于实例内部的嵌套类,您甚至可以通过使用 __subclasscheck____instancecheck__。但是,它不会比其他更简单的方法获得任何好处(比如__init__ 的附加参数)。它只会限制你可以用它做什么,而且我发现 Java 中的内部类在我每次必须使用它们时都非常混乱。

      【讨论】:

        【解决方案3】:

        Python 允许您使用 C++03 或 Java 中需要一个类的函数(包括 lambdas)做很多事情(尽管 Java 有匿名内部类,所以嵌套类并不总是看起来像您的示例)。听众,访客,诸如此类。列表推导式大致是一种访问者:

        Python:

        (foo(x) if x.f == target else bar(x) for x in bazes)
        

        C++:

        struct FooBar {
            Sommat operator()(const Baz &x) const {
                return (x.f == val) ? foo(x) : bar(x);
            }
            FooBar(int val) : val(val) {}
            int val;
        };
        
        vector<Sommat> v(bazes.size());
        std::transform(bazes.begin(), bazes.end(), v.begin(), FooBar(target));
        

        然后 C++ 和 Java 程序员问自己的问题是,“我正在编写的这个小类:它应该出现在与需要使用它的大类相同的范围内,还是应该将它限制在范围内唯一使用它的类?"[*]

        由于您不想发布该事物,或允许其他任何人依赖它,因此在这些情况下的答案通常是嵌套类。在 Java 中,私有类可以提供服务,而在 C++ 中,您可以将类限制为 TU,在这种情况下,您可能不再关心名称出现在哪个命名空间范围内,因此实际上不需要嵌套类。它只是一种风格,加上 Java 提供了一些语法糖。

        正如其他人所说,另一种情况是 C++ 中的迭代器。 Python 可以在没有迭代器类的情况下支持迭代,但是如果你正在用 C++ 或 Java 编写数据结构,那么你必须把 blighters 放在某个地方。要遵循标准库容器接口,无论类是否嵌套,您都将拥有一个嵌套的 typedef,因此很自然地认为“嵌套类”。

        [*] 他们还问自己,“我应该只写一个 for 循环吗?”,但让我们假设一个答案是否定的情况......

        【讨论】:

          【解决方案4】:

          将一个类放在另一个类中的主要原因是避免使用仅在一个类中使用的东西污染全局命名空间,因此不属于全局命名空间。这甚至适用于 Python,全局命名空间是特定模块的命名空间。例如,如果您有 SomeClass 和 OtherClass,并且它们都需要以专门的方式读取某些内容,则最好有 SomeClass.Reader 和 OtherClass.Reader 而不是 SomeClassReader 和 OtherClassReader。

          不过,我从未在 C++ 中遇到过这种情况。 It can be problematic to control access to the outer class' fields from a nested class. 在头文件中定义的编译单元中只有一个公共类和在 CPP 文件中定义的一些实用程序类也很常见(Qt 库就是一个很好的例子)。这样它们对“外人”是不可见的,这很好,因此将它们包含在标题中没有多大意义。它还有助于增加二进制兼容性,否则很难维护。好吧,无论如何,这很痛苦,但要少得多。

          嵌套类真正有用的语言的一个很好的例子是Java。那里的嵌套类自动有一个指向创建它们的外部类的实例的指针(除非您将内部类声明为静态)。这样您就不需要将“外部”传递给它们的构造函数,并且您可以仅通过它们的名称来寻址外部类的字段。

          【讨论】:

          • 在 Python 中,你宁愿把东西放在模块中。全局命名空间中通常很少有东西,因为全局命名空间并不是真正的全局。所以我认为这不适用于 Python。
          • @Lennart,我的意思当然是模块的全局命名空间。我将编辑我的答案以纠正此问题。如果您在一个模块中有五个类,并且每个类都需要以一种专门的方式读取某些内容,那么通常最好在每个全局类中都有一个 Reader 嵌套类,而不是 SomeClassReader、OtherClassReader 等。
          • 是的,在大多数情况下,您在 Python 中宁愿拥有单独的模块。但这是一个有效的答案,所以无论如何你都会得到 +1。 :)
          • @Lennart,单独的模块有什么用?对于甚至不属于同一模块的全局命名空间的东西?我不明白。如果 SomeClass 属于模块 mymodule,如果读取器类仅由 SomeClass 在内部使用,您会将 SomeClass 的读取器放在哪里?
          • 我可以说“与什么相同的模块”? :-) 如果您想将事物与命名空间分开,您通常使用单独的模块。在这种情况下,您可能会将每个 SomeClass 粘贴在 somemodule 中,而不是 mymodule 中,然后将其导入 mymodule。或者也许让 mymodule 成为一个命名空间。但是,是的,make 是一个嵌套类也是一种有效的方法。但从未见过真正做到这一点,除非是来自其他语言并且是 Python 新手。
          【解决方案5】:

          至少在 C++ 中,嵌套类的一个主要常见用例是容器中的迭代器。例如,假设的实现可能如下所示:

          class list
          {
             public:
          
             class iterator 
             {
                // implementation code
             };
          
             class const_iterator
             {
                // implementation code
             };
          };
          

          在 C++ 中嵌套类的另一个原因是私有实现细节,例如地图、链表等的节点类。

          【讨论】:

            【解决方案6】:

            我不是 python 的忠实粉丝,但对我来说,这种类型的决策更符合语义而非语法。如果你正在实现一个列表,List 中的类 Node 本身并不是一个可以在任何地方使用的类,而是列表的一个实现细节。同时,您可以在TreeGraph 中拥有一个Node 内部类。编译器/解释器是否允许您访问该类是另一回事。编程是关于编写计算机可以遵循并且其他程序员可以阅读的规范,List.Node 更明确地表明NodeList 的内部,而不是将ListNode 作为一级类。

            【讨论】:

              【解决方案7】:

              在某些语言中,嵌套类可以访问外部类范围内的变量。 (类似于函数,或者函数中的类嵌套。当然,函数中的嵌套只是创建一个方法,其行为相当不足为奇。;))

              在更专业的术语中,我们创建一个closure

              【讨论】:

              • 对,由于 Python 的显式性和显式自身,这在 Python 中也变得毫无用处,因为如果您是显式的,您可以从任何地方访问所有内容,甚至您自己的实例成员也无法隐式访问。跨度>
              【解决方案8】:

              它允许你控制嵌套类的访问——例如,它经常用于实现细节类。在 C++ 中,它在解析各种事物的时间以及无需先声明即可访问的内容方面也具有优势。

              【讨论】:

              • 啊,当然! 拍脑门 Python没有访问控制,原来OOP理论的这部分是错误的,私有成员只是一个障碍。
              • 我不认为私人会员是一个障碍。容器类的用户不应该能够访问 std::map&lt;K,V&gt;::rb_node 之类的东西。
              • C++ 和 Python 在很多方面都有不同的底层设计目标——Python 没有访问控制是有道理的,但对于 C++ 来说却不是这样。
              • 在像 C++ 这样的低级语言中可能存在安全原因。我还没用过就知道了。但我对此表示怀疑,使用该类的程序员只会发现一个更糟糕的黑客,其中有更多的漏洞。 :) 但是私人会员在 Java 和 Delphi 中多次困扰着我,我使用了多年。我从来没有在 Python 中需要它。
              • @Lennart:有一个你完全没有考虑的原因:例如,拥有一个私有方法允许你非常积极地内联它 - 当你编译它时。禁止您调用私有成员或字段的一个非常好的理由是,根据编译过程的激进程度,它们实际上可能不存在
              猜你喜欢
              • 2014-08-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-11-26
              • 2010-12-05
              • 1970-01-01
              • 2016-03-13
              相关资源
              最近更新 更多