Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nesting classes 3 levels deep within Python - Is it bad practice?

I'm working on a Python script at work that is used to interact with XLS/XLSX/CSV spreadsheets. There are three main classes which are nested inside one another (not extending one another, the classes are literally inside the other class)

The three primary classes are explained below:

  1. The primary Workbook class, which is a factory method for the XLS/XLSX/CSV classes. This is accessible externally
  2. The private __Worksheet class within the Workbook class, which is used to open a specific spreadsheet or worksheet within the file itself. This is accessible only via the Workbook.worksheet() method
  3. The private __Cell class within the __Worksheet class, which interacts with the cells themselves. This shouldn't be accessible externally, rather only accessible via the __Worksheet class

Heres a simplified version of the class structures thus far:

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

So the question I have is - Is it considered bad practice to have a class within a class, within a class?

I'm somewhat new to Python, my experience is mostly PHP/JS/Perl. It doesn't seem too uncommon in Python to have a class within a class, but for some reason, nesting a class 3 levels deep just seems wrong. If it is, and there's a better way to do it, then that would be great.

I know the alternative is to not nest the classes, and just check if an instance of Workbook is given to Worksheet as a parameter. Then create a method in Workbook which just returns an instance if Worksheet, while handing self as one of the parameters used to initiate it.

Example:

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

However, this alternative means that the Worksheet and Cell classes are accessible externally and can be initiated manually... but I guess thats not a terrible thing.

Let me know what you think the best route is! One of the comments on this post provides a link to another SO post, in which a user posts 3 advantages of nesting classes, the first of which is:

Logical grouping of classes: If a class is useful to only one other class then it is logical to embed it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.

Which is exactly what I was thinking. I just thought it was somewhat awkward nesting them to 3 layers, as I've only done 2 before.

like image 366
Justin Avatar asked Oct 19 '25 10:10

Justin


1 Answers

Turning your question around: is there any advantage in writing the classes as nested? Unlike functions, classes don't use "lexical scoping" (i.e. unlike functions, a name that can't be resolved from the current class's namespace will not be resolved from surrounding classes). This is why you have to refer to the __Worksheet class relative to an instance of Workbook.

This in turn means that there is no perceptible advantage to the nesting that you have employed, though it will work. Most experienced Python programmers would probably write your example without using nesting. This simplifies the code (because the class names are all global to the containing module).

Note that this is quite different from declaring a class inside a function body. In that case the code of the class can refer to variables from the function's namespace, and Python goes to great lengths to ensure that those references are still available to the class even after the function call has terminated and the local namespace associated with the call has been destroyed.

like image 76
holdenweb Avatar answered Oct 22 '25 00:10

holdenweb



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!