Utilizzo di una chiusura per evitare la duplicazione del codice in Python

5

A volte mi ritrovo a voler eseguire lo stesso codice da punti diversi nella stessa funzione. Supponiamo di avere qualche funzione func1 e voglio fare la stessa cosa da alcuni punti diversi in func1. Normalmente il modo per farlo sarebbe scrivere un'altra funzione, chiamarla "func2", e chiamare func2 da diversi posti in func1. Ma che dire quando è conveniente avere le variabili di accesso func2 che sono locali a func1? Mi trovo a scrivere una chiusura. Ecco un esempio forzato:

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)

Questo è il modo Pythonico per gestire questo problema? Una chiusura sembra una macchina piuttosto pesante da far girare qui, soprattutto dato che devo costruire una nuova funzione ogni volta che viene chiamata func1, anche se quella funzione sarà fondamentalmente la stessa ogni volta. Ma evita il codice duplicato e in pratica l'overhead di creare ripetutamente func2 non ha importanza per me.

    
posta kuzzooroo 23.04.2014 - 01:40
fonte

3 risposte

3

È una forma accettabile. Come ha detto @Giorgio, metterei la chiusura dopo la definizione della variabile acquisita per facilitare il flusso di lettura.

La forma alternativa sarebbe quella di definire un'altra funzione, prendendo a, b, c come parametri. Questo è 5 parametri che è molto. La chiusura ti consente di evitare di ripetere te stesso in un modo molto semplice. Questa è una grande vittoria per la tua versione.

È possibile utilizzare il modulo timeit per confrontare le prestazioni di snippet semplici. Verifica che una chiusura non sia un heavy machinery . L'unico problema che vedo è che crea più elementi nidificati. Quindi se ti ritrovi a scrivere una grande chiusura, dovresti cercare di estrarre la parte complessa all'esterno. Ma in questo caso non penso che sia un 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")
    
risposta data 23.04.2014 - 07:01
fonte
2

Non sono un fan dello stile di OP qui. La funzione interna non è testabile e non riutilizzabile. Inoltre, se la funzione interna o esterna cresce significativamente lungo / complessa, diventa un disordine per leggere / eseguire il debug / rivedere.

Per brevi interni e amp; funzioni esterne, questo modello è forse accettabile, ma se la funzione interiore è lunga o se ci sono più di queste funzioni, troverei una definizione di classe più leggibile.

Varianti che preferisco:

  • Avere una funzione esterna che prende alcuni parametri (quelli che prevedi di riutilizzare) e restituisce una funzione di chiusura / interiore che prende solo i parametri che devono variare tra i casi.
  • Definisci & istanziare una classe che memorizza le variabili ripetute su sé
  • Definisci una classe di parametri comuni e crea funzioni che ne prendono in considerazione le istanze. Tutti i parametri ripetuti vengono quindi compressi in uno solo.

Ciascuna di queste altre opzioni è più testabile, più riutilizzabile e più estensibile.

    
risposta data 08.05.2018 - 17:11
fonte
0

TL; DR: Crea funzione (idealmente al momento dell'importazione, non del tempo di esecuzione) a meno che non sia necessario salvare lo stato

Una chiusura viene così denominata perché "chiudi" una variabile lessicale nell'ambito che racchiude. Questo ti permette di salvare lo stato. Ecco perché usi le chiusure.

Se a, b e c rappresentano le variabili dichiarate in func1 hai una chiusura. Se non rappresentano le variabili catturate dall'ambito di inclusione, hai semplicemente una funzione dichiarata all'interno di una funzione.

Se desideri acquisire valori, ma non li modificherò, userei functools.partial invece di una chiusura o una funzione definita dinamicamente. vale a dire.

from functools import partial

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

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

Vorrei ancora rendere func1 una normale funzione di supporto dichiarata al di fuori di func2 . Quindi, se possibile, utilizza partial per salvare gli argomenti che passano nelle chiamate successive.

    
risposta data 23.04.2014 - 06:30
fonte

Leggi altre domande sui tag