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:
- The primary
Workbook
class, which is a factory method for the XLS/XLSX/CSV classes. This is accessible externally - The private
__Worksheet
class within theWorkbook
class, which is used to open a specific spreadsheet or worksheet within the file itself. This is accessible only via theWorkbook.worksheet()
method - 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.