在Python中定义另一个类中的类有什么好处?

时间:2008-09-17 01:02:31

标签: python oop

我在这里讨论的是嵌套类。基本上,我有两个我正在建模的类。 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())

7 个答案:

答案 0 :(得分:106)

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

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

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

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

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

    class cls2(object):
       ...

然后从另一个模块,你可以导入Group并将它们称为Group.cls1,Group.cls2等。但是有人可能会说你可以通过使用模块完成相同的操作(可能以一种不那么令人困惑的方式)。

答案 1 :(得分:19)

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

类嵌套完全是关于范围的。如果你认为一个类只在另一个类的上下文中有意义,那么前者可能是成为嵌套类的好选择。

这是一个常见的模式,将辅助类作为私有的嵌套类。

答案 2 :(得分:6)

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

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

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

通过弄乱元类,以及在库存标准类中嵌套类,你可以获得什么,更难以阅读代码,更难理解代码,以及奇怪的错误,这些错误非常难以理解,而不是非常熟悉为什么'class'范围与任何其他python范围完全不同。

答案 3 :(得分:5)

您可以使用类作为类生成器。喜欢(在一些袖口代码中:):

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'''

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

答案 4 :(得分:4)

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

见这个例子:

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()对象和{{1}时,行foo.bar将构造foo正在构造的对象实际上是一个foo2.bar对象时的对象。

如果类foo2在类bar之外定义,以及它的继承版本(例如,将被称为foo),则定义新类{{ 1}}会更痛苦,因为bar2的构造函数需要将第一行替换为foo2,这意味着重写整个构造函数。

答案 5 :(得分:1)

不,构图并不意味着嵌套。 如果你想在外层类的命名空间中隐藏它,那么拥有一个嵌套类是有意义的。

无论如何,我认为在您的情况下嵌套没有任何实际用途。它会使代码更难阅读(理解),并且还会增加缩进,这会使行更短,更容易分裂。

答案 6 :(得分: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() 调用它即可。