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

Prints:Les générateurs peuvent-ils être utilisés avec string.format en python?

'1, 2, 3' 

Cela fonctionne, tant que le nombre de {} dans format ne dépasse pas la longueur d'un tuple. Je veux le faire fonctionner pour un tuple de longueur arbitraire, en le complétant avec - s'il est de longueur insuffisante. Et pour éviter de faire des hypothèses sur le nombre de {}, je voulais utiliser un générateur. Voici ce que j'avais à l'esprit:

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

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

attendu:

'1, 2, -' 

Mais il ne revient jamais. Pouvez-vous le faire fonctionner avec des générateurs? Est-ce qu'il y a une meilleure approche?

Répondre

3

Si vous y réfléchissez, outre le fait que le déballage des arguments variables se décompose d'un coup, il y a aussi le fait que format ne prend pas nécessairement ses arguments dans l'ordre, comme dans '{2} {1} {0}'.

Vous pouvez contourner ce problème si format vient de prendre une séquence au lieu d'exiger des arguments séparés, en créant une séquence qui fait ce qu'il faut. Voici un exemple trivial:

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

Bien sûr, votre version de la vie réelle enveloppait un itérables arbitraire, pas sous-classe list, et aurait probablement utiliser tee ou un cache interne et tirer dans de nouvelles valeurs comme l'a demandé, seulement en défaut quand tu as passé la fin.(Vous pouvez rechercher des recettes "paresseuses" ou "paresseuses" sur ActiveState, car il y en a quelques-unes qui le font.) Mais cela suffit pour montrer l'exemple.

Maintenant, comment cela nous aide-t-il? Ce n'est pas le cas; *lst sur un DefaultList essayera juste de faire un tuple de la chose, nous donnant exactement le même nombre d'arguments que nous avions déjà. Mais que se passerait-il si vous aviez une version de format qui pourrait simplement prendre une séquence d'arguments à la place? Alors vous pourriez juste passer votre DefaultList et cela fonctionnerait.

Et vous avez cela: Formatter.vformat.

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

Cependant, il existe un moyen encore plus facile, une fois que vous utilisez Formatter explicitement au lieu de implicitement par la méthode str. Vous pouvez simplement remplacer la méthode get_value et/ou son 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], {})) 

Bien sûr, vous allez encore avoir besoin d'envelopper votre iterator dans quelque chose qui fournit le protocole de séquence.


Pendant que nous y sommes, votre problème pourrait être résolu plus directement si la langue avait un protocole de « déballer itérables ». Voir here pour un thread python-idées proposant une telle chose, et tous les problèmes que l'idée a. (Notez également que la fonction format rendrait cela plus délicat, car il faudrait utiliser le protocole de décompression directement au lieu de s'appuyer sur l'interpréteur pour le faire comme par magie, mais en supposant qu'il le fasse, vous auriez juste besoin d'écrire wrapper simple et polyvalent autour de tout itérable qui gère __unpack__ pour cela.)

4

Vous ne pouvez pas utiliser des générateurs sans fin pour remplir tout*args appel d'arguments arbitraires. Python effectue une itération sur le générateur pour charger tous les arguments à transmettre à l'appelable, et si le générateur est sans fin, cela ne se terminera jamais.

Vous pouvez utiliser des générateurs sans fin sans problème. Vous pouvez utiliser itertools.islice() pour coiffer un générateur:

from itertools import islice 

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

Après tout, vous savez déjà combien de slots votre modèle a.

+0

Got it. Pouvez-vous suggérer une meilleure approche? Je ne suis pas content de produire un générateur d'une longueur maximale, c'est gaspilleur (cela va à l'encontre du but d'utiliser un générateur, une liste ferait l'affaire) et ne serait pas garanti de toujours fonctionner. – user443854

+0

@ user443854: Vous pouvez utiliser 'itertools.islice()' pour limiter un générateur. –

+0

Je suis conscient de 'itertools.islice()', mais je ne vois pas comment cela s'applique ici. Je devrais connaître le nombre d'éléments nécessaires avant de pouvoir l'utiliser. J'espérais réaliser quelque chose de différent. En anglais, je veux dire à l'interprète: voici un générateur, répétez-le autant de fois que nécessaire, mais pas plus. – user443854

3

Martijn Pieters a la réponse immédiate, mais si vous vouliez créer une sorte de wrapper/helper générique pour format autofilling, vous pouvez regarder string.Formatter.parse. En utilisant cela, vous pouvez obtenir une représentation de la façon dont format voit la chaîne de format, et dépouiller le nombre d'arguments/noms d'arguments nommés pour déterminer de façon dynamique combien de temps votre itérateur doit être.

1

L'approche naïve consisterait à fournir des arguments L/2 à la fonction de format où L est la longueur de la chaîne de format. Depuis un jeton de remplacement est d'au moins 2 caractères de long, vous êtes certain d'avoir toujours assez de valeurs pour décompresser:

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

Comme le suggère Silas Ray une supérieure peut être trouvée plus raffinée liée à l'aide 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))) 
Questions connexes