Usando um encerramento para evitar duplicação de código em Python

5

Por vezes, encontro-me a querer executar o mesmo código a partir de alguns pontos diferentes na mesma função. Digamos que eu tenha alguma função func1, e eu quero fazer a mesma coisa a partir de alguns pontos diferentes em func1. Normalmente, a maneira de fazer isso seria escrever outra função, chamar "func2", e chamar func2 de vários lugares diferentes em func1. Mas e quando é conveniente ter variáveis de acesso func2 que são locais para func1? Eu me vejo escrevendo um fechamento. Aqui está um exemplo artificial:

import random
import string

def func1 (param1, param2):
    def func2(foo, bar):
        print "{0} {1} {2:0.2f} {3} {4} {0}".format('*'*a, b, c, foo, bar)

    a = random.randrange(10)
    b = ''.join(random.choice(string.letters) for i in xrange(10))
    c = random.gauss(0, 1)
    if param1:
        func2(a*c, param1)
    else:
        if param2 > 0:
            func2(param2, param2)

Esta é a maneira pitônica de lidar com esse problema? Parece que um fechamento de máquinas muito pesadas está rolando aqui, especialmente considerando que eu tenho que construir uma nova função toda vez que func1 é chamada, mesmo que essa função seja basicamente a mesma o tempo todo. Mas evita o código duplicado e, na prática, a sobrecarga de criar repetidamente o func2 não importa para mim.

    
por kuzzooroo 23.04.2014 / 01:40
fonte

3 respostas

3

É uma forma aceitável. Como disse @Giorgio, eu colocaria o fechamento após a definição da variável capturada para facilitar o fluxo de leitura.

A forma alternativa seria definir outra função, tomando a, b, c como parâmetros. Isso é 5 parâmetros que é muito. O fechamento permite que você evite se repetir de maneira muito simples. Esta é uma grande vitória para a sua versão.

Você pode usar o módulo timeit para comparar o desempenho de trechos simples. Você deve verificar se o encerramento não é um heavy machinery . O único problema que vejo é que ele cria mais elementos aninhados. Então, se você estiver escrevendo um grande fechamento, você deve tentar extrair a parte complexa do lado de fora. Mas neste caso não acho que seja um problema.

import timeit
import random
import string

def func1 (param1, param2):
    def func2(foo, bar):
        return "{0} {1} {2:0.2f} {3} {4} {0}".format('*'*a, b, c, foo, bar)

    a = random.randrange(10)
    b = ''.join(random.choice(string.letters) for i in xrange(10))
    c = random.gauss(0, 1)
    if param1:
        func2(a*c, param1)
    else:
        if param2 > 0:
            func2(param2, param2)

def func4(foo, bar, a, b, c):
    return "{0} {1} {2:0.2f} {3} {4} {0}".format('*'*a, b, c, foo, bar)

def func3 (param1, param2):

    a = random.randrange(10)
    b = ''.join(random.choice(string.letters) for i in xrange(10))
    c = random.gauss(0, 1)
    if param1:
        func4(a*c, param1, a, b, c)
    else:
        if param2 > 0:
            func4(param2, param2, a, b, c)

print timeit.timeit('func1("tets", "")',
 number=100000,
 setup="from __main__ import func1")

print timeit.timeit('func3("tets", "")',
 number=100000,
 setup="from __main__ import func3")
    
por 23.04.2014 / 07:01
fonte
2

Eu não sou fã do estilo do OP aqui. A função interna não é testável e não é reutilizável. Além disso, se a função interna ou externa crescer significativamente longa / complexa, ela se tornará uma bagunça para ler / depurar / revisar.

Para abreviações internas & funções externas, esse padrão é talvez aceitável, mas se a função interna for longa ou se houver várias funções, eu encontraria uma definição de classe mais legível.

Variantes que considero preferíveis:

  • Possua uma função externa que utiliza alguns parâmetros (aqueles que você espera reutilizar) e retorna uma função interna / de fechamento que só recebe parâmetros que precisam variar entre os casos.
  • Definir & instanciar uma classe que armazena as variáveis repetidas no self
  • Defina uma classe de parâmetros comuns e crie funções que tomem instâncias dela. Todos os parâmetros repetidos são comprimidos para um.

Cada uma dessas outras opções é mais testável, mais reutilizável e mais extensível.

    
por 08.05.2018 / 17:11
fonte
0

TL; DR: Cria uma função (idealmente no momento da importação, não do tempo de execução), a menos que você precise salvar o estado

Um fechamento é assim chamado porque você "fecha" uma variável léxica no escopo incluído. Isso permite que você salve o estado. É por isso que você usa encerramentos.

Se a, b e c representarem variáveis declaradas em func1 , você terá um fechamento. Se eles não representam as variáveis capturadas do escopo de inclusão, você só tem uma função declarada dentro de uma função.

Se você deseja capturar valores, mas não vai modificá-los, eu usaria functools.partial em vez de um encerramento ou uma função definida dinamicamente. ou seja,

from functools import partial

def func1(a, b):    
  print a, b

def func2(i, j ,k):
  fn = partial(func1, i)
  fn(j)
  fn(k)

Eu ainda tornaria func1 uma função auxiliar regular declarada fora de func2 . Em seguida, use partial para economizar na transmissão de argumentos sobre as chamadas subsequentes.

    
por 23.04.2014 / 06:30
fonte