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.)
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
@ user443854: Vous pouvez utiliser 'itertools.islice()' pour limiter un générateur. –
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