Acho que o # 1 é, na verdade, uma opção ruim.
Vamos considerar sua função:
def the_public_method(self, param1, param2):
self.var4 = param1
self.var5 = param2
self.var6 = param1 + param2 * self.var1
self.__suboperation1()
self.__suboperation2()
self.__suboperation3()
Quais partes de dados suboperation1 usa? Coleta dados usados por suboperação2? Quando você passa dados por meio de armazenamento em si mesmo, não sei dizer como as partes da funcionalidade estão relacionadas. Quando olho para mim, alguns dos atributos são do construtor, alguns da chamada para o_public_method e alguns adicionados aleatoriamente em outros lugares. Na minha opinião, é uma bagunça.
E o número 2? Bem, em primeiro lugar, vamos analisar o segundo problema:
Also, the functions would be closely related to
each other, but wouldn't be grouped together.
Eles estariam em um módulo juntos, então eles seriam totalmente agrupados juntos.
The problem I see with this is that I have to pass a lot of variables
between functions.
Na minha opinião, isso é bom. Isso torna explícitas as dependências de dados em seu algoritmo. Ao armazená-los, seja nas variáveis globais ou em um self, você oculta as dependências e as faz parecer menos ruins, mas elas ainda estão lá.
Normalmente, quando esta situação surge, significa que você não encontrou o caminho certo para decompor o seu problema. Você está achando estranho dividir-se em várias funções porque está tentando dividir o caminho errado.
É claro que, sem ver sua função real, é difícil adivinhar o que seria uma boa sugestão. Mas você dá um pouco do que você está lidando aqui:
My program needs to do a relatively complex operation a number of
times. That operation requires a lot of "bookkeeping", has to create
and delete some temporary files etc.
Deixe-me escolher um exemplo de algo que se encaixa na sua descrição, um instalador. Um instalador tem que copiar um monte de arquivos, mas se você cancelá-lo no meio, você precisa retroceder todo o processo, incluindo colocar de volta os arquivos que você substituiu. O algoritmo para isso parece algo como:
def install_program():
copied_files = []
try:
for filename in FILES_TO_COPY:
temporary_file = create_temporary_file()
copy(target_filename(filename), temporary_file)
copied_files = [target_filename(filename), temporary_file)
copy(source_filename(filename), target_filename(filename))
except CancelledException:
for source_file, temp_file in copied_files:
copy(temp_file, source_file)
else:
for source_file, temp_file in copied_files:
delete(temp_file)
Agora, multiplique essa lógica por ter que fazer configurações de registro, ícones de programas, etc., e você terá uma grande bagunça.
Acho que sua solução nº 1 se parece com:
class Installer:
def install(self):
try:
self.copy_new_files()
except CancellationError:
self.restore_original_files()
else:
self.remove_temp_files()
Isso torna o algoritmo geral mais claro, mas oculta a maneira como as diferentes partes se comunicam.
A abordagem # 2 é parecida com:
def install_program():
try:
temp_files = copy_new_files()
except CancellationError as error:
restore_old_files(error.files_that_were_copied)
else:
remove_temp_files(temp_files)
Agora é explícito como partes dos dados se movem entre as funções, mas é muito estranho.
Então, como essa função deve ser escrita?
def install_program():
with FileTransactionLog() as file_transaction_log:
copy_new_files(file_transaction_log)
O objeto FileTransactionLog é um gerenciador de contexto. Quando copy_new_files copia um arquivo, ele o faz através do FileTransactionLog, que manipula a cópia temporária e rastreia quais arquivos foram copiados. No caso de exceção, ele copia os arquivos originais de volta e, no caso de sucesso, apaga as cópias temporárias.
Isso funciona porque encontramos uma decomposição mais natural da tarefa. Anteriormente, estávamos misturando a lógica sobre como instalar o aplicativo com a lógica sobre como recuperar uma instalação cancelada. Agora, o log de transações manipula todos os detalhes sobre arquivos temporários e registros, e a função pode se concentrar no algoritmo básico.
Eu suspeito que seu caso esteja no mesmo barco. Você precisa extrair os elementos de contabilidade em algum tipo de objeto para que sua tarefa complexa possa ser expressa de maneira mais simples e elegante.