Paradigma per gestire la lista di cose o singoli elementi

5

Molto spesso durante la codifica in Python (o in qualsiasi altra lingua in cui il tipo non è specificato) mi imbatto nel seguente problema:

Voglio scrivere una funzione che possa accettare una cosa o un contenitore di tali cose in generale. Ad esempio, considera questa funzione sottoinsieme:

def subset(df, months=all_months):
    return df[pd.DatetimeIndex(df['Timestamp']).month.isin(months)]

Vorrei ora poterlo chiamare sia con un elenco di mesi, sia con un mese. Il secondo caso tuttavia è possibile solo con qualcosa di brutto come:

subset(df, months=["June"]) # Why not just subset(df, "June")?

Posso pensare a 2 soluzioni, nessuna delle due mi sembra elegante:

  • Posso usare isinstance e trattare scalari diversi dai contenitori. Questo non è il massimo perché scrivo check e ci sono anche casi in cui il codice nella funzione è complesso e necessita di una revisione maggiore quando si tratta di uno scalare invece che di un elenco

  • Potrei trasmettere l'argomento alla lista: months = [months] . Anche in questo caso utilizzerei isinstance .

C'è una soluzione migliore di quella che ho pensato? Ancora meglio, c'è una soluzione ben nota di cui non sono a conoscenza?

Modifica

Entrambe le risposte hanno fornito soluzioni accettabili. Ho deciso di attenermi a quello proposto da @Doc Brown perché posso facilmente isolare il tipo di controllo in un decoratore e voglio evitare di introdurre lo scartare nell'API per motivi specifici del luogo di lavoro.

    
posta pilu 05.09.2017 - 14:52
fonte

3 risposte

11

Utilizza un elenco di argomenti:

def subset(df, *months):
    for month in months: ...

subset(df, 1)
subset(df, 1, 2)
subset(df, *[1, 2])

È più facile per gli utenti della tua api capire come funzionano le cose quando le funzioni funzionano esattamente in un modo. Comportarsi in modo diverso a seconda di ciò che sei passato può essere molto confuso.

    
risposta data 05.09.2017 - 14:57
fonte
2

L'uso dei controlli di tipo (ad esempio, utilizzando isinstance ) può essere una soluzione ragionevole (anche l'implementazione della funzione isin utilizzata per scopi dimostrativi fa uso di esso). In una lingua con digitazione dinamica come Python, non esiste un sovraccarico di funzioni come nei linguaggi tipizzati staticamente. Spesso l'approccio più pulito e semplice per distinguere tra diversi tipi di parametri è infatti l'utilizzo di isinstance , soprattutto quando si desidera risolvere un problema in modo generico.

Tuttavia, si deve fare attenzione a non rilevare le stringhe qui come sequenze (che sono sequenze di caratteri). Vuoi solo "una sequenza di stringhe" o una singola "stringa" come valida. Dato che vuoi supportare raccolte (o stringhe) predefinite come input, puoi eseguire il controllo del tipo richiesto con una funzione di supporto come questa:

def makeSequence(obj):
    if type(obj) in [list, tuple, xrange]:
        return obj
    else:
        return [obj]

Quindi con questa implementazione

def subset(df, months=all_months):
    months=makeSequence(months)
    # ... maybe some complex code dealing with a collection of months ...
    return df[pd.DatetimeIndex(df['Timestamp']).month.isin(months)]

il codice non ha bisogno di "revisione principale quando si tratta di uno scalare invece che di un elenco" , si tratta di una modifica semplice e diretta alla funzione che aggiunge appena una riga all'inizio e lascia la già esistente codice non toccato.

Si noti inoltre che la "cattiva reputazione" di funzioni come isinstance o il tipo di controllo del tipo di cui sopra è probabilmente causata da casi in cui il controllo del tipo viene abusato in un modo che viola il principio Open-Closed. Quando, ad esempio, isinstance viene chiamato per un elenco di tipi diversi, forse da una gerarchia di classi, con la gestione di casi speciali per ogni tipo, i nuovi requisiti spesso portano alla necessità di non dimenticare di estendere tale elenco. In tale situazione l'utilizzo di isinstance diventa soggetto a errori e dovrebbe essere evitato. Tuttavia, il caso d'uso sopra non è esattamente lo stesso: per questo caso d'uso, makeSequence probabilmente ha bisogno solo di supportare le raccolte predefinite di Python, quindi nulla che cambierà molto frequentemente.

Una volta che hai un livello base di tali funzioni che funziona anche su scalari ed elenchi, altre funzioni basate su quel livello non devono più implementare un trattamento così speciale. Quindi potresti provare a implementare un tale livello?

    
risposta data 05.09.2017 - 16:31
fonte
0

Potresti semplicemente fare affidamento sulla grammatica inglese:

def subset(df, *, months=None, month=None):
    if months is None:
        if month is None:
            raise ValueError('subset must be called with either month= or months= parameter')
        else:
            months = [month]

    return df[pd.DatetimeIndex(df['Timestamp']).month.isin(months)]
    
risposta data 24.09.2017 - 05:47
fonte

Leggi altre domande sui tag