简体   繁体   English

嵌套类是否有用例?

[英]Is there a usecase for nested classes?

I've recently seen several people doing things like this here on Stackoverflow: 我最近看到有几个人在Stackoverflow上做这样的事情:

class A:
    foo = 1

    class B:
        def blah(self):
            pass

In other words, they have nested classes. 换句话说,他们有嵌套类。 This works (although people new to Python seem to run into problems because it doesn't behave like they thought it would), but I can't think of any reason to do this in any language at all, and certainly not in Python. 这是有效的(虽然Python新手似乎遇到了问题,因为它的行为并不像他们想象的那样),但我想不出任何理由在任何语言中都这样做,当然也不是在Python中。 Is there such a usecase? 有这样的用例吗? Why are people doing this? 为什么人们这样做? Searching for this it seems it's reasonably common in C++, is there a good reason there? 搜索它似乎在C ++中相当普遍,有充分的理由吗?

The main reason for putting one class in another is to avoid polluting the global namespace with things that are used only inside one class and therefore doesn't belong in the global namespace. 将一个类放在另一个类中的主要原因是避免使用仅在一个类中使用的内容来污染全局名称空间,因此不属于全局名称空间。 This is applicable even to Python, with the global namespace being a namespace of a particular module. 这甚至适用于Python,全局命名空间是特定模块的命名空间。 For example if you have SomeClass and OtherClass, and both of them need to read something in a specialized way, it is better to have SomeClass.Reader and OtherClass.Reader rather than SomeClassReader and OtherClassReader. 例如,如果您有SomeClass和OtherClass,并且它们都需要以专门的方式读取内容,那么最好使用SomeClass.Reader和OtherClass.Reader而不是SomeClassReader和OtherClassReader。

I have never encountered this in C++, though. 不过,我从未在C ++中遇到过这种情况。 It can be problematic to control access to the outer class' fields from a nested class. 从嵌套类控制对外部类字段的访问可能会有问题。 And it is also pretty common to have just one public class in a compilation unit defined in the header file and some utility classes defined in the CPP file (the Qt library is a great example of this). 在头文件中定义的编译单元中只有一个公共类以及CPP文件中定义的一些实用程序类(Qt库就是一个很好的例子)也很常见。 This way they aren't visible to "outsiders" which is good, so it doesn't make much sense to include them in the header. 这样它们对于“外人”是不可见的,这是好的,所以将它们包含在标题中没有多大意义。 It also helps to increase binary compatibility which is otherwise a pain to maintain. 它还有助于增加二进制兼容性,否则这是一个难以维护的问题。 Well, it's a pain anyway, but much less so. 嗯,无论如何,这是一种痛苦,但更不用说了。

A great example of a language where nested classes are really useful is Java. 嵌套类非常有用的语言的一个很好的例子是Java。 Nested classes there automatically have a pointer to the instance of the outer class that creates them (unless you declare the inner class as static). 嵌套类自动有一个指向创建它们的外部类的实例的指针(除非您将内部类声明为静态)。 This way you don't need to pass "outer" to their constructors and you can address the outer class' fields just by their names. 这样您就不需要将“外部”传递给它们的构造函数,只需按名称来解析外部类的字段。

It allows you to control the access of the nested class- for example, it's often used for implementation detail classes. 它允许您控制嵌套类的访问 - 例如,它通常用于实现详细信息类。 In C++ it also has advantages in terms of when various things are parsed and what you can access without having to declare first. 在C ++中,它还具有解析各种内容的时间以及无需先声明即可访问的内容。

I am not a big fan of python, but to me this type of decisions are more semantical than syntactical. 我不是python的忠实粉丝,但对我来说这种类型的决策比语法更具语义性。 If you are implementing a list, the class Node inside List is not a class in itself meant to be used from anywhere, but an implementation detail of the list. 如果您正在实现一个列表,那么List的类Node本身并不是一个可以在任何地方使用的类,而是列表的实现细节。 At the same time you can have a Node internal class inside Tree , or Graph . 同时,您可以在TreeGraph拥有Node内部类。 Whether the compiler/interpreter allows you to access the class or not is in a different thing. 编译器/解释器是否允许您访问类是另一回事。 Programing is about writing specifications that the computer can follow and other programers can read, List.Node is more explicit in that Node is internal to List than having ListNode as a first level class. 编程是关于编写计算机可以遵循的规范以及其他List.Node可以读取的List.NodeList.Node更明确,因为NodeList内部而不是将ListNode作为第一级类。

In some languages, the nested class will have access to variables that are in scope within the outer class. 在某些语言中,嵌套类可以访问外部类中的变量。 (Similarly with functions, or with class-in-function nesting. Of course, function-in-class nesting just creates a method, which behaves fairly unsurprisingly. ;) ) (与函数或类函数嵌套类似。当然,函数类嵌套只是创建一个方法,其行为相当不出人意料。;))

In more technical terms, we create a closure . 在更多技术术语中,我们创建了一个闭包

Python lets you do a lot of things with functions (including lambdas) that in C++03 or Java you need a class for (although Java has anonymous inner classes, so a nested class doesn't always look like your example). Python允许你使用函数(包括lambdas)做很多事情,在C ++ 03或Java中你需要一个类(虽然Java有匿名内部类,所以嵌套类并不总是看起来像你的例子)。 Listeners, visitors, that kind of thing. 听众,访客,那种事。 A list comprehension is loosely a kind of visitor: 列表理解是一种松散的访问者:

Python: 蟒蛇:

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

C++: 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));

The question that C++ and Java programmers then ask themselves is, "this little class that I'm writing: should it appear in the same scope as the big class that needs to use it, or should I confine it within the scope of the only class that uses it?"[*] C ++和Java程序员随后问自己的问题是,“我正在编写的这个小类:它应该出现在与需要使用它的大类相同的范围内,还是应该将它限制在唯一的范围内使用它的类?“[*]

Since you don't want to publish the thing, or allow anyone else to rely on it, often the answer in these cases is a nested class. 由于您不想发布该内容,或者允许其他人依赖它,因此在这些情况下,答案通常是嵌套类。 In Java, private classes can serve, and in C++ you can restrict classes to a TU, in which case you may no longer care too much what namespace scope the name appears in, so nested classes aren't actually required. 在Java中,私有类可以提供服务,在C ++中,您可以将类限制为TU,在这种情况下,您可能不再关心名称出现的命名空间范围,因此实际上不需要嵌套类。 It's just a style thing, plus Java provides some syntactic sugar. 这只是一种风格,加上Java提供了一些语法糖。

As someone else said, another case is iterators in C++. 正如其他人所说,另一种情况是C ++中的迭代器。 Python can support iteration without an iterator class, but if you're writing a data structure in C++ or Java then you have to put the blighters somewhere. Python可以在没有迭代器类的情况下支持迭代,但是如果你用C ++或Java编写数据结构,那么你必须将blighter放在某个地方。 To follow the standard library container interface you'll have a nested typedef for it whether the class is nested or not, so it's fairly natural to think, "nested class". 要遵循标准的库容器接口,无论该类是否嵌套,您都将拥有嵌套的typedef,因此认为“嵌套类”是相当自然的。

[*] They also ask themselves, "should I just write a for loop?", but let's suppose a case where the answer to that is no... [*]他们也会问自己,“我应该写一个for循环吗?”,但让我们假设一个案例,答案是否定的......

In C++ at least, one major common use-case for nested classes is iterators in containers. 至少在C ++中,嵌套类的一个主要常见用例是容器中的迭代器。 For example, a hypothetical implementation might look something like this: 例如,假设的实现可能如下所示:

class list
{
   public:

   class iterator 
   {
      // implementation code
   };

   class const_iterator
   {
      // implementation code
   };
};

Another reason for nested classes in C++ would be private implementation details like node classes for maps, linked lists, etc. C ++中嵌套类的另一个原因是私有实现细节,如地图,链表等的节点类。

"Nested classes" can mean two different things, which can be split into three different categories by intent. “嵌套类”可以表示两种不同的东西,可以按意图分为三个不同的类别。 The first one is purely stylistic, the other two are used for practical purposes, and are highly dependent on the features language where they are used. 第一个是纯粹的风格,另外两个用于实际目的,并且高度依赖于使用它们的特征语言。

  1. Nested class definitions for the sake of creating a new namespace and/or organizing your code better. 嵌套类定义,用于创建新命名空间和/或更好地组织代码。 For example, in Java this is accomplished through the use static nested classes, and it is suggested by the official documentation as a way to create more readable and maintainable code, and to logically group classes together. 例如,在Java中,这是通过使用静态嵌套类来完成的,并且官方文档建议将其作为创建更易读和可维护的代码以及将类逻辑分组在一起的方法。 The Zen of Python, however, suggests that you nest code blocks less, thus discouraging this practice. 然而,Python的Zen表明你较少嵌套代码块,从而阻碍了这种做法。

    import this

    In Python you'd much more often see the classes grouped in modules. 在Python中,您经常会看到按模块分组的类。

  2. Putting a class inside another class as part of its interface (or the interface of the instances). 将一个类放在另一个类中作为其接口(或实例的接口)的一部分。 First, this interface can be used by the implementation to aid subclassing, for example imagine a nested class HTML.Node which you can override in a subclass of HTML to alter the class used to create new node instances. 首先,实现可以使用此接口来辅助子类化,例如想象一个嵌套类HTML.Node ,您可以在HTML的子类中重写该类以更改用于创建新节点实例的类。 Second, this interface might be used by the class/instance users, though this is not that useful unless you are in the third case described below. 其次,类/实例用户可能会使用此接口,但除非您处于下面描述的第三种情况,否则这不是很有用。

    In Python at least, you don't need to nest the definitions to achieve either of those, however, and it's probably very rare. 至少在Python中,你不需要嵌套定义来实现其中任何一个,但它可能非常罕见。 Instead, you might see Node defined outside of the class and then node_factory = Node in the class definition (or a method dedicated to creating the nodes). 相反,您可能会在类定义之外看到Node定义,然后在类定义中看到node_factory = Node (或者专用于创建节点的方法)。

  3. Nesting the namespace of the objects, or creating different contexts for different groups of objects. 嵌套对象的命名空间,或为不同的对象组创建不同的上下文。 In Java, non-static nested classes (called inner classes) are bound to an instance of the outer class. 在Java中, 非静态嵌套类(称为内部类)绑定到外部类的实例。 This is very useful because it lets you have instances of the inner class that live inside different outer namespaces. 这非常有用,因为它允许您拥有位于不同外部命名空间内的内部类的实例。

    For Python, consider the decimal module. 对于Python,请考虑decimal模块。 You can create different contexts, and have things like different precisions defined for each context. 您可以创建不同的上下文,并为每个上下文定义不同的精度。 Each Decimal object can assigned a context on creation. 每个Decimal对象都可以在创建时分配上下文。 This achieves the same as an inner class would, through a different mechanism. 这通过不同的机制实现了与内部阶级相同的效果。 If Python supported inner classes, and Context and Decimal were nested, you'd have context.Decimal('3') instead of Decimal('3', context=context) . 如果Python支持内部类,并且ContextDecimal是嵌套的,那么你将拥有context.Decimal('3')而不是Decimal('3', context=context)

    You could easily create a metaclass in Python that lets you create nested classes that live inside of an instance, you can even make it produce proper bound and unbound class proxies that support isinstance correctly through the use of __subclasscheck__ and __instancecheck__ . 您可以轻松地在Python中创建一个元类,它允许您创建实体内部的嵌套类,甚至可以通过使用__subclasscheck____instancecheck__来生成正确支持isinstance绑定和未绑定类代理。 However, it won't gain you anything over the other simpler ways to achieve the same (like an additional argument to __init__ ). 但是,它不会通过其他更简单的方法获得任何东西(例如__init__的附加参数)。 It would only limit what you can do with it, and I have found inner classes in Java very confusing every time I had to use them. 它只会限制你可以用它做什么,而且每次我必须使用它时,我发现Java中的内部类非常混乱。

In Python, a more useful pattern is declaration of a class inside a function or method. 在Python中,更有用的模式是在函数或方法中声明类。 Declaration of a class in the body of another class, as people have noted in other answers, is of little use - yes, it does avoid pollution of the module namespace, but since there_is_ a module namespace at all, a few more names on it do not bother. 正如人们在其他答案中所指出的那样,在另一个类的主体中声明一个类没什么用处 - 是的,它确实避免了对模块名称空间的污染,但是因为那里有一个模块命名空间,所以还有更多的名字不打扰。 Even if the extra classes are not intended to be instantiated directly by users of the module, putting then on the module root make their documentation more easily accessible to others. 即使额外的类不打算由模块的用户直接实例化,然后在模块根目录上使其他人更容易访问他们的文档。

However, a class inside a function is a completely different creature: It is "declared" and created each time the code containing the class body is run. 但是,函数内部的类是完全不同的生物:每次运行包含类主体的代码时,它都会被“声明”并创建。 This gives one the possibility of creating dynamic classes for various uses - in a very simple way. 这使得人们可以以非常简单的方式为各种用途创建动态类。 For example, each class created this way is in a different closure, and can have access to different instances of the variables on the containing function. 例如,以这种方式创建的每个类都在不同的闭包中,并且可以访问包含函数的变量的不同实例。

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()

And using it on the console: 并在控制台上使用它:

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

So, a simple call_counter decorator could use a static "Counter" class, defined outside the function, and receiving func as a parameter to its constructor - but if you want to tweak other behaviors, like in this example, making repr(func) return the function representation, not the class representation, it is easier to be made this way. 因此,一个简单的call_counter装饰器可以使用在函数外部定义的静态“Counter”类,并接收func作为其构造函数的参数 - 但是如果你想调整其他行为,比如在这个例子中,使repr(func)返回函数表示,而不是类表示,更容易这样做。 .

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM