在Python中深入嵌套3级 - 这是不好的做法吗?

时间:2016-08-03 16:11:34

标签: python class oop subclass

我正在开发一个用于与XLS / XLSX / CSV电子表格交互的Python脚本。有三个主要类相互嵌套(相互扩展,类实际上在另一个类中)

以下解释了三个主要类别:

  1. Workbook类,它是XLS / XLSX / CSV类的工厂方法。这可以从外部访问
  2. __Worksheet类中的私有Workbook类,用于在文件本身内打开特定的电子表格或工作表。只能通过Workbook.worksheet()方法
  3. 访问
  4. __Cell类中的私有__Worksheet类,它与单元格本身进行交互。这不应该在外部访问,而只能通过__Worksheet
  5. 访问

    到目前为止,这是类结构的简化版本:

    class Workbook( object ):
    
        def __init__( self, file_name ):
            self.__file_name = file_name
    
        def worksheet( self, name ):
            return self.__Worksheet( self, name )
    
        class __Worksheet():
            def __init__( self, workbook, worksheet ):
                self.__workbook = workbook
    
            def cell( self, cell_id, data = None ):
                return self.__Cell( cell_id, data )
    
            class __Cell():
                def __init__( self, cell, data = None ):
                    self.__cell = cell
                    self.__data = data
    
                def setVal( self, data ):
                    self.__data = data
    
                def __str__( self ):
                    return self.__data
    
    workbook = Workbook( 'test-file.csv' )
    worksheet = workbook.worksheet( 'First Worksheet' )
    cell_A1 = worksheet.cell('A1', 'Foo...')
    
    print("Before - Cell A1: %s" % cell_A1) # => Before - Cell A1: Foo...
    cell_A1.setVal('Bar...')
    print("After - Cell A1: %s" % cell_A1)  # => Before - After - Cell A1: Bar...
    

    所以我的问题是 - 在一个班级内,班级内有一个班级被认为是不好的做法吗?

    我对Python有点新手,我的经验主要是PHP / JS / Perl。在Python中创建类中的类似乎并不常见,但由于某种原因,将类3级深度嵌套似乎是错误的。如果是,并且有更好的方法,那就太棒了。

    我知道另一种方法是嵌套类,只需检查Workbook的实例是否作为参数提供给Worksheet。然后在Workbook中创建一个方法,只返回Worksheet的实例,同时将self作为用于启动它的参数之一。

    示例:

    class Workbook( object ):
        def __init__( self, file_name ):
            self.__file_name = file_name
    
        def worksheet( self, name ):
            return self.Worksheet( self, name )
    
    class Worksheet( object ):
        def __init__( self, workbook, worksheet = 0 ):
            if not isinstance( workbook, Workbook ):
                raise Exception( 'Expected the workbook to be an instance of the Workbook class' )
    
            self.__workbook = workbook
    
        def cell( self, cell_id, data = None ):
            return self.Cell( cell_id, data )
    
    class Cell( object ):
        def __init__( self, worksheet, cell, data = None ):
            if not isinstance( worksheet, Worksheet ):
                raise Exception( 'Expected the worksheet to be an instance of the Worksheet class' )
    
            self.__cell = cell
            self.__data = data
    
        def setVal( self, data ):
            self.__data = data
    
        def __str__( self ):
            return self.__data
    
    # Example Usage One
    workbook = Workbook( 'test-file.xls' )
    worksheet = workbook.worksheet( 'First Worksheet' )
    cell_A1 = worksheet.cell('A1', 'Foo...')
    
    print("Before - Cell A1: %s" % cell_A1) # => Before - Cell A1: Foo...
    cell_A1.setVal('Bar...')
    print("After - Cell A1: %s" % cell_A1)  # => Before - After - Cell A1: Bar...
    
    # Example Usage Two
    workbook = Workbook( 'test-file.xlsx' )
    worksheet = Worksheet( workbook, 'First Worksheet' )
    cell_A1 = Cell( worksheet, 'A1', 'Foo...')
    
    print("Before - Cell A1: %s" % cell_A1) # => Before - Cell A1: Foo...
    cell_A1.setVal('Bar...')
    print("After - Cell A1: %s" % cell_A1)  # => Before - After - Cell A1: Bar...
    
    # Failed Example
    worksheet = Worksheet( 'Not worksheet', 1 ) # => Exception, as expected
    

    但是,这种替代方法意味着WorksheetCell类可以从外部访问,并且可以手动启动......但我想这不是一件可怕的事情。

    让我知道你认为最好的路线是什么! 其中一篇评论提供了another SO post的链接,其中一位用户发布了嵌套类的3个优点,其中第一个是:

      

    类的逻辑分组:如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的。嵌套这样的“辅助类”使得它们的包更加简化。

    这正是我的想法。我只是觉得将它们嵌套到3层有点尴尬,因为我之前只做过2次。

2 个答案:

答案 0 :(得分:4)

转向你的问题:将这些类编写为嵌套是否有任何优势?与函数不同,类不使用“词法作用域”(即。与函数不同,无法从当前类的名称空间解析的名称不会从周围的类中解析)。这就是为什么你必须引用相对于__Worksheet实例的Workbook类。

这反过来意味着你所使用的嵌套没有明显的优势,尽管它会起作用。最有经验的Python程序员可能会在不使用嵌套的情况下编写您的示例。这简化了代码(因为类名对于包含模块都是全局的)。

请注意,这与在函数体内声明类完全不同。在这种情况下,类的代码可以引用函数命名空间中的变量,并且Python竭尽全力确保即使在函数调用终止并且与调用关联的本地命名空间之后,这些引用仍然可用于类。被摧毁了。

答案 1 :(得分:1)

抛开其他地方提出的一些问题,比如你是否需要所有这些课程,我会说不:最好不要嵌套这些课程。仅记录如何使用它们会更好。考虑使类本身不那么吸引人的替代名称,例如WorksheetReference和CellReference。抛出几个文档字符串,即使他们只是说:

  

WorksheetReference并不是要直接实例化。您应该通过工作簿的worksheet()方法访问它。

本答复的其余部分将研究原因。

嵌套的成本和收益是什么?

在您的案例中嵌套类实际上只提供了额外的嵌套。这使得讨论嵌套的类变得更加困难:它不是Worksheet,它是Workbook.__Worksheet(实际上它是Workbook._Workbook__Worksheet,但是谈论更加麻烦)。由于讨论起来比较困难,因此很难记录界面,因此有人知道如何使用从myworkbook.worksheet调用回来的内容。你的文档是否会说"返回一个带有单元格方法的对象"或"返回工作簿.__工作表对象"或完全采取不同的方法?

这种嵌套都不会阻止某人通过探索来尝试实例化该类。因此,如果您真的关心防止"误用",您仍然需要检查传递给工作表的__init__函数的值(稍后会详细介绍)。

为什么要关注如何强制执行使用模式?

python的原则之一是" consenting adults"。也就是说,我们最好不要展示和记录使用某种东西的正确方法,并相信人们在没有充分理由的情况下不会破坏它。如果创建没有工作簿的工作表或没有工作表的单元格没有任何好处,那么没人会打扰。

另一个原则是duck typing。这主要是因为允许编程松散地指定接口是有益的。如果有人想创建一个实现Workbook足够方法的类,那么通常可以在任何可以使用Workbook实例的地方使用它。即使它在内部做了一些非常不同的事情。结合同意的成年人,这表明让某人尝试这样做是有益的。 __init__方法中的嵌套和基于类型的验证都与此作斗争。

试图防止误用

正如我刚才所述,我一般都不建议这样做。但是,如果你这样做,你应该做得很好。建议检查您的备用__init__函数是非常麻烦的,因为它会鼓励不良做法。由于它抛出类型Exception的异常,因此捕获它的唯一方法是捕获所有异常。相反,由于您要验证参数的类型,因此您应该考虑抛出更具体的异常,例如TypeError,或者可能(在更大的代码体中)甚至是其自定义子类。

对方法的进一步调整

我同意一条评论,询问为什么WorksheetCells存在。按原样,帮助班级的好处很少;帮助程序类可以是实现细节,也可能完全省略。为了更简单地记录此类实现细节,您可能需要考虑通过__getitem__和/或__setitem__实现其中一个或两个,从而启用如下代码:

worksheet = workbook['First Worksheet']
worksheet['A1'] = 'Foo...'

我假设在您的真实代码中,它们将比您已经显示的方法具有更多功能。您可能仍然需要考虑__getitem__和/或__setitem__访问者,但是还有足够的其他理由来公开和记录类。

作为附加功能的一部分,将访问现有工作表和单元格的方法与可以创建它们的方法完全分开可能会有所帮助。

何时嵌套可能是正确的?

这个特殊的例子似乎与这种情况不符,但有时候通过类似的界面对事物进行分组是有意义的。假设您可能有多个Workbook子类需要实例化Worksheet的不同子类,而这些子类又可能需要实例化Cell的不同子类。利用类继承机制将其存储在类中可能很有用。

请注意,即使这种情况也不要求类本身嵌套;包含这样的行就足够了:

class Workbook:
    # : : :
    worksheet_type = Worksheet
    # : : :
    def worksheet(self, name):
        return self.worksheet_type(self, name)

class WorkbookVariant(Workbook):
    # : : :
    worksheet_type = WorkbookVariant