2013-08-27 5 views
6
"{}, {}, {}".format(*(1,2,3,4,5)) 

Stampe:I generatori possono essere usati con string.format in python?

'1, 2, 3' 

questo funziona, a condizione che il numero di {} in format non supera la lunghezza di una tupla. Voglio farlo funzionare per una tupla di lunghezza arbitraria, imbottendola con - se è di lunghezza insufficiente. E per evitare di fare supposizioni sul numero di {}, volevo usare un generatore. Ecco quello che avevo in mente:

def tup(*args): 
    for s in itertools.chain(args, itertools.repeat('-')): 
     yield s 

print "{}, {}, {}".format(*tup(1,2)) 

atteso:

'1, 2, -' 

Ma non è mai ritorna. Puoi farlo funzionare con i generatori? C'è un approccio migliore?

risposta

3

Se ci pensate, oltre al fatto che l'argomento della variabile decompressione scompatta tutto in una volta, c'è anche il fatto che lo format non necessariamente prende i suoi argomenti in ordine, come in '{2} {1} {0}'.

Si potrebbe aggirare questo se format ha appena preso una sequenza invece di richiedere argomenti separati, costruendo una sequenza che fa la cosa giusta. Ecco un esempio banale:

class DefaultList(list): 
    def __getitem__(self, idx): 
     try: 
      return super(DefaultList, self).__getitem__(idx) 
     except IndexError: 
      return '-' 

Naturalmente la versione vita reale sarebbe avvolgere un iterabile arbitrario, non sottoclasse list, e probabilmente dovuto usare tee o una cache interna e tirare in nuovi valori come richiesto, solo inadempiente quando hai passato la fine(Si consiglia di cercare le ricette "lista pigra" o "sequenza lenta" in ActiveState, perché ce ne sono alcune che lo fanno.) Ma questo è sufficiente per mostrare l'esempio.

Ora, come ci aiuta? Non lo fa; *lst su un DefaultList proverò a creare una tupla, fornendo esattamente lo stesso numero di argomenti che abbiamo già avuto. Ma cosa succede se avessi una versione di format che potrebbe semplicemente prendere una sequenza di arg invece? Quindi potresti semplicemente passare il tuo DefaultList e funzionerebbe.

E questo è quello: Formatter.vformat.

>>> string.Formatter().vformat('{0} {1} {2}', DefaultList([0, 1]), {}) 
'0 1 -' 

Tuttavia, c'è un modo ancora più semplice, una volta che si sta utilizzando Formatter esplicitamente invece che implicitamente attraverso il metodo str. Si può solo sovrascrivere il metodo get_value e/o la sua check_unused_args:

class DefaultFormatter(string.Formatter): 
    def __init__(self, default): 
     self.default = default 

    # Allow excess arguments 
    def check_unused_args(self, used_args, args, kwargs): 
     pass 

    # Fill in missing arguments 
    def get_value(self, key, args, kwargs): 
     try: 
      return super(DefaultFormatter, self).get_value(key, args, kwargs) 
     except IndexError: 
      return '-' 

f = DefaultFormatter('-') 

print(f.vformat('{0} {2}', [0], {})) 
print(f.vformat('{0} {2}', [0, 1, 2, 3], {})) 

Naturalmente si sta ancora andando ad avere bisogno di avvolgere il vostro iteratore in qualcosa che fornisce il protocollo sequenza.


Mentre noi siamo, il problema potrebbe essere risolto in modo più diretto, se la lingua ha avuto un protocollo "iterabile disimballaggio". Vedi here per un thread di idee in python che propone una cosa del genere e tutti i problemi che l'idea ha. (Si noti inoltre che la funzione format renderebbe questo più complicato, poiché sarebbe necessario utilizzare direttamente il protocollo di decompressione invece di affidarsi all'interprete per eseguirlo magicamente, ma, supponendo che lo facesse, sarebbe sufficiente scrivere un involucro semplice e generico intorno a qualsiasi iterable che gestisce __unpack__ per esso.)

4

Non è possibile utilizzare generatori infiniti per riempire *args chiamata di argomenti arbitrari.

Python esegue iterazioni sul generatore per caricare tutti gli argomenti da passare al callable e, se il generatore è infinito, non verrà mai completato.

È possibile utilizzare generatori non infiniti senza problemi. Si potrebbe utilizzare itertools.islice() per coronare un generatore:

from itertools import islice 

print "{}, {}, {}".format(*islice(tup(1,2), 3)) 

Dopo tutto, è già sapere quanti slot ha il modello.

+0

Capito. Puoi suggerire un approccio migliore? Non sono contento di produrre un generatore di una lunghezza massima, che è uno spreco (vanifica lo scopo di usare un generatore, una lista farebbe) e non sarebbe garantito che funzioni sempre. – user443854

+0

@ user443854: È possibile utilizzare 'itertools.islice()' per limitare un generatore. –

+0

Sono a conoscenza di 'itertools.islice()', ma non vedo come si applica qui. Avrei bisogno di conoscere il numero di elementi necessari prima che io possa usarlo. Speravo di ottenere qualcosa di diverso. In parole povere, voglio dire all'interprete: ecco un generatore, ripetilo tutte le volte che è necessario, ma non di più. – user443854

3

Martijn Pieters ha la risposta immediata, ma se si desidera creare una sorta di wrapper/helper generico per il riempimento automatico format, è possibile consultare string.Formatter.parse. Usando questo, è possibile ottenere una rappresentazione di come format vede la stringa di formato e rimuovere il numero di argomento/nome dell'argomento con nome per capire in modo dinamico quanto deve essere lungo il proprio iteratore.

1

L'approccio ingenuo sarebbe quello di fornire argomenti L/2 alla funzione di formato dove L è la lunghezza della stringa di formato. Dal momento che un token di sostituzione è di almeno 2 caratteri, si è certi di avere sempre i valori sufficienti per decomprimere:

def tup(l, *args): 
    for s in args + (('-',) * l): 
     yield s 
s = "{}, {}, {}" 
print s.format(*list(tup(len(s)//2, 1, 2))) 

Come suggerito da Silas Ray un più raffinato limite superiore può essere trovato usando string.Formatter.parse

import string 
def tup(l, *args): 
    for s in args + (('-',) * l): 
     yield s 
s = "{}, {}, {}" 
l = len(list(string.Formatter().parse(s))) 
print s.format(*list(tup(l, 1, 2)))