2011-08-24 13 views
5

Supponiamo che tu abbia una funzione che deve mantenere una sorta di stato e comportarsi diversamente a seconda di quello stato. Sono a conoscenza di due modi per implementare questo, dove lo stato è memorizzato esclusivamente dalla funzione:Python - Attributi di funzione o valori predefiniti mutabili

  1. Utilizzando un attributo funzione di
  2. Utilizzando un valore di default mutabile

Utilizzando una versione leggermente modificata di Felix Klings answer to another question , qui è una funzione di esempio che può essere utilizzato in re.sub() modo che solo la terza partita di una regex sarà sostituito:

attributo funzione:

def replace(match): 
    replace.c = getattr(replace, "c", 0) + 1 
    return repl if replace.c == 3 else match.group(0) 

Mutevole valore predefinito:

def replace(match, c=[0]): 
    c[0] += 1 
    return repl if c[0] == 3 else match.group(0) 

Per me la prima sembra più pulito, ma ho visto il secondo più comunemente. Quale è preferibile e perché?

+4

La gente semplicemente non pensa della funzione attributi perché non sono che ampiamente utilizzati da principianti, ma _everyone_ pensa di utilizzare il valore predefinito mutevole perché _ogni_ principiante viene morso da loro almeno una volta. – agf

risposta

4

Io uso invece la chiusura, senza effetti collaterali.

Ecco l'esempio (ho appena modificato l'esempio originale di Felix Klings answer):

def replaceNthWith(n, replacement): 
    c = [0] 
    def replace(match): 
     c[0] += 1 
     return replacement if c[0] == n else match.group(0) 
    return replace 

E l'utilizzo:

# reset state (in our case count, c=0) for each string manipulation 
re.sub(pattern, replaceNthWith(n, replacement), str1) 
re.sub(pattern, replaceNthWith(n, replacement), str2) 
#or persist state between calls 
replace = replaceNthWith(n, replacement) 
re.sub(pattern, replace, str1) 
re.sub(pattern, replace, str2) 

Per mutabili quello che dovrebbe accadere se qualcuno chiama sostituire (partita , c = [])?

Per attributo che si è rotto l'incapsulamento (sì, lo so che Python non ha implementato in classi da motivi diff ...)

+0

+1 concordato: non si è reso conto che il contesto della domanda * originale * è una funzione annidata. – Seth

+0

@ JasonR.Coombs Ho aggiunto un esempio –

+0

ah! confondere 'c = [0]' per 'c [0]' ... – n611x007

1

ne dite:

  1. utilizzare una classe
  2. utilizzare una variabile globale

È vero, questi non vengono memorizzati interamente all'interno della funzione. Io probabilmente utilizzare una classe:

class Replacer(object): 
    c = 0 
    @staticmethod # if you like 
    def replace(match): 
     replace.c += 1 
     ... 

Per rispondere alla tua domanda effettiva, utilizzare getattr. È un modo molto chiaro e leggibile per archiviare i dati per dopo. Dovrebbe essere abbastanza ovvio per qualcuno che lo sta leggendo quello che stai cercando di fare.

La versione dell'argomento di default mutabile è un esempio di errore di programmazione comune (presupponendo che si otterrà una nuova lista ogni volta). Solo per questo motivo lo eviterei. Qualcuno che lo legge più tardi potrebbe decidere che è una buona idea senza comprenderne appieno le conseguenze. E anche in questo caso, sembra che la tua funzione funzioni solo una volta (il tuo valore c non viene mai azzerato).

1

Per me, entrambi questi approcci sembrano poco raccomandabile. Il problema è chiedere un'istanza di classe. Normalmente non pensiamo alle funzioni come al mantenimento dello stato tra le chiamate; questo è ciò che le classi sono per.

Detto questo, ho usato attributi di funzione prima per questo genere di cose. In particolare se si tratta di una funzione one-shot definita all'interno di un altro codice (cioè non è possibile utilizzarla da nessun'altra parte), limitarsi ad attribuire gli attributi ad essa è più concisa che definire una nuova classe e creare un'istanza di essa.

Non vorrei mai abusare di valori predefiniti per questo. C'è una grande barriera alla comprensione perché lo scopo naturale dei valori predefiniti è di fornire valori predefiniti degli argomenti, non di mantenere lo stato tra le chiamate. Inoltre, un argomento predefinito ti invita a fornire un valore non predefinito e in genere ottieni un comportamento molto strano se lo fai con una funzione che abusa dei valori predefiniti per mantenere lo stato.

2

Entrambi i modi mi sembrano strani. Il primo però è molto meglio. Ma quando ci pensi in questo modo: "Qualcosa con uno stato che può fare operazioni con quello stato e un input aggiuntivo", suona davvero come un oggetto normale. E quando qualcosa suona come un oggetto, dovrebbe essere un oggetto ... SO, la mia soluzione sarebbe quella di utilizzare un semplice oggetto con un metodo __call__:

class StatefulReplace(object): 
    def __init__(self, initial_c=0): 
     self.c = initial_c 
    def __call__(self, match): 
     self.c += 1 
     return repl if self.c == 3 else match.group(0) 

e quindi si può scrivere nello spazio globale o init modulo:

replace = StatefulReplace(0) 
Problemi correlati