Universal Spreadsheet reader/writer
Indeed, this factory pattern is not right. A factory should have the single responsibility of creating instances. You violated that the moment you made it a subclass of ISheetIO, which is an entirely different responsibility.

The first step to make this better is to change the factory to be more factory-like:


Rename it to SheetIOFactory
Extend object (stop extending ISheetIO)
Rename the _factory method to create_sheet method: this will become the official way to create sheets


The methods that remain, read_sheet, write_sheet, _orient_data, _transpose belong in another class. Let's call it SheetIOHelper.
This class should not extend ISheetIO.

Why? You would rightfully ask:
it has read_sheet and write_sheet methods.
It even uses the same kind of parameters (uhm, more or less).
But a crucial (and somewhat subtle) difference is that the methods in the helper class and the methods in ExcelSheet have completely different contracts:


The methods in ExcelSheet are designed to work with Excel sheets
The methods in SheetIOHelper are designed to work with whatever kind of sheet you throw at them


That is, the methods in SheetIOHelper are expected to detect appropriate source / target sheet type, the methods in ExcelSheet are just not designed that way, they work with only a specific type of sheet.
In strongly typed languages,
these two interfaces wouldn't be compatible, the code wouldn't compile.

In other words,
the helper and the concrete classes don't follow the same interface.
You could create a dedicated parent class for SheetIOHelper.
That might sound like overkill,
since you only plan to have just one implementation,
it might be worth it,
to make the distinction from ISheetIO loud and clear.
Or you could be lazy and just make it extend object.

And when you use the helper,
you should add "helper" in the variable name to avoid confusion:

def ConvertSheet(in_sheet, out_sheet)
    sheet_io_helper = SheetIOHelper()
    data_matrix = sheet_io_helper.read_sheet(in_sheet)
    # different functions would operate on data_matrix before writing.
    sheet_io_helper.write_sheet(out_sheet, modified_data)




Actually,
since you probably won't ever work with the concrete classes directly,
it would be better to rename the interface of the concrete classes instead.
So in the end, have this class structure:


SheetIOHelper: interface of the helper, that can read / write any types of sheet

SheetIO: implementation of the helper

ConcreteSheetIO: interface of concrete implementations that can only read / write sheets of type T

ExcelSheetIO: concrete implementation to read / write Excel sheets
CsvSheetIO: concrete implementation to read / write CSV sheets

SheetIOFactory: implementation of the factory, creates ConcreteSheetIO instances and does nothing else


In the end, the names are quite different from what I used in the beginning.
I hope you see the evolution that lead us here, and hopefully it's not too confusing.
If this is not clear,
let me know, I can restructure your original if it would help.

Code review

In this code:


def read_sheet(self, sheet, col_start=0, row_start=0, col_cnt=-1, row_cnt=-1):
    # ...
    if row_cnt &lt; 0:
        row_cnt += sheet.nrows + 1
    if col_cnt &lt; 0:
        col_cnt += sheet.ncols + 1



When I see row_cnt += sheet.nrows + 1 I have 2 thoughts:


What is the value of row_cnt before the += ?
Why the +1 in sheet.nrows + 1 ?


These are very easy to answer, but I still have to think of them.
It would be better if I didn't have to think.
It seems to me that this simpler variant would be just as good,
and it won't make me think:

if row_cnt &lt; 0:
    row_cnt = sheet.nrows
if col_cnt &lt; 0:
    col_cnt = sheet.ncols




In this code:


if sheet_type is xlrd.sheet or sheet_type is xlwt.WorkSheet:
    return ExcelSheet()
# elif CSV
#    return CSVSheet()
# elif OpenXML
#    return OpenXMLSheet()
else:
    raise TypeError("Unknown Sheet type or not a sheet")



It's a minor thing,
but since each elif branch returns,
I prefer this writing style:

if sheet_type is xlrd.sheet or sheet_type is xlwt.WorkSheet:
    return ExcelSheet()
# if CSV
#    return CSVSheet()
# if OpenXML
#    return OpenXMLSheet()
raise TypeError("Unknown Sheet type or not a sheet")