125

我在这里谈论的是嵌套类。本质上,我有两个正在建模的课程。一个 DownloadManager 类和一个 DownloadThread 类。这里明显的 OOP 概念是组合。然而,组合并不一定意味着嵌套,对吧?

我的代码看起来像这样:

class DownloadThread:
    def foo(self):
        pass

class DownloadManager():
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadThread())

但现在我想知道是否存在嵌套更好的情况。就像是:

class DownloadManager():
    class DownloadThread:
        def foo(self):
            pass
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadManager.DownloadThread())
4

7 回答 7

144

当“内部”类是一次性的时,您可能希望这样做,它永远不会在外部类的定义之外使用。例如要使用元类,有时这样做很方便

class Foo(object):
    class __metaclass__(type):
        .... 

如果您只使用一次,而不是单独定义元类。

唯一一次我使用这样的嵌套类时,我只使用外部类作为命名空间来将一堆密切相关的类组合在一起:

class Group(object):
    class cls1(object):
       ...

    class cls2(object):
       ...

然后从另一个模块中,您可以导入 Group 并将它们称为 Group.cls1、Group.cls2 等。但是有人可能会争辩说,您可以通过使用模块完成完全相同的操作(也许以一种不那么容易混淆的方式)。

于 2008-09-17T01:12:59.480 回答
25

我不知道 Python,但你的问题似乎很笼统。如果它特定于 Python,请忽略我。

类嵌套是关于范围的。如果您认为一个类仅在另一个类的上下文中才有意义,那么前者可能是成为嵌套类的良好候选者。

将帮助类作为私有的嵌套类是一种常见的模式。

于 2008-09-17T01:14:33.687 回答
9

嵌套类还有另一种用法,当人们想要构造其增强功能被封装在特定嵌套类中的继承类时。

看这个例子:

class foo:

  class bar:
    ...  # functionalities of a specific sub-feature of foo

  def __init__(self):
    self.a = self.bar()
    ...

  ...  # other features of foo


class foo2(foo):

  class bar(foo.bar):
    ... # enhanced functionalities for this specific feature

  def __init__(self):
    foo.__init__(self)

请注意,在 的构造函数中,当正在构造的对象实际上是一个对象时foo,该行将self.a = self.bar()构造 a foo.bar,而当正在构造的对象实际上是一个foo对象foo2.bar时,该行将构造一个foo2对象。

如果类bar是在类之外定义的foo,以及它的继承版本(bar2例如,将被调用),那么定义新类foo2会更加痛苦,因为构造函数的foo2第一行需要替换为self.a = bar2(),这意味着重写整个构造函数。

于 2018-04-13T08:46:20.140 回答
6

你可以使用一个类作为类生成器。喜欢(在一些即兴代码中:)

class gen(object):
    class base_1(object): pass
    ...
    class base_n(object): pass

    def __init__(self, ...):
        ...
    def mk_cls(self, ..., type):
        '''makes a class based on the type passed in, the current state of
           the class, and the other inputs to the method'''

我觉得当您需要此功能时,您会非常清楚。如果您不需要做类似的事情,那么它可能不是一个好的用例。

于 2008-09-17T01:34:41.767 回答
5

这样做确实没有任何好处,除非您正在处理元类。

类:套件真的不是你想象的那样。这是一个奇怪的范围,它会做一些奇怪的事情。真的连课都不上!它只是收集一些变量的一种方式——类的名称、基础、属性的小字典和元类。

名称、字典和基础都传递给元类函数,然后将其分配给 class:suite 所在范围内的变量“name”。

通过弄乱元类,实际上是通过在标准类中嵌套类,您可以获得什么,更难阅读代码,更难理解代码,以及在不熟悉为什么“类”的情况下非常难以理解的奇怪错误范围与任何其他 python 范围完全不同。

于 2008-09-17T01:52:55.263 回答
1

此功能的一个很好的用例是错误/异常处理,例如:

class DownloadManager(object):
    class DowndloadException(Exception):
        pass

    def download(self):
        ...

现在阅读代码的人知道与此类相关的所有可能的异常。

于 2021-08-17T14:26:53.150 回答
0

无论哪种方式,在类内部或外部定义都可以。这是一个员工工资计划程序,其中帮助类 EmpInit 嵌入到 Employee 类中:

class   Employee:

    def level(self, j):
        return j * 5E3

    def __init__(self, name, deg, yrs):
        self.name = name
        self.deg = deg
        self.yrs = yrs
        self.empInit = Employee.EmpInit(self.deg, self.level)
        self.base = Employee.EmpInit(self.deg, self.level).pay

    def pay(self):
        if self.deg in self.base:
            return self.base[self.deg]() + self.level(self.yrs)
        print(f"Degree {self.deg} is not in the database {self.base.keys()}")
        return 0

    class   EmpInit:

        def __init__(self, deg, level):
            self.level = level
            self.j = deg
            self.pay = {1: self.t1, 2: self.t2, 3: self.t3}

        def t1(self):   return self.level(1*self.j)
        def t2(self):   return self.level(2*self.j)
        def t3(self):   return self.level(3*self.j)

if  __name__ == '__main__':
    for loop in range(10):
        lst = [item for item in input(f"Enter name, degree and years : ").split(' ')]
        e1 = Employee(lst[0], int(lst[1]), int(lst[2]))
        print(f'Employee {e1.name} with degree {e1.deg} and years {e1.yrs} is making {e1.pay()} dollars')
        print("EmpInit deg {0}\nlevel {1}\npay[deg]: {2}".format(e1.empInit.j, e1.empInit.level, e1.base[e1.empInit.j]))

要在外部定义它,只需取消缩进 EmpInit 并将 Employee.EmpInit() 更改为简单的 EmpInit() 作为常规的“has-a”组合。但是,由于 Employee 是 EmpInit 的控制器并且用户不直接实例化或与它交互,因此在内部定义它是有意义的,因为它不是一个独立的类。另请注意,实例方法 level() 被设计为在此处的两个类中调用。因此,它也可以方便地定义为 Employee 中的静态方法,这样我们就不需要将它传递给 EmpInit,而只需使用 Employee.level() 调用它。

于 2021-04-01T07:49:31.137 回答