Can generators be used with string.format in python?

"{}, {}, {}".format(*(1,2,3,4,5)) 

Print

 '1, 2, 3' 

This works if the number {} in format does not exceed the length of the tuple. I want it to work for a tuple of arbitrary length, filling it - if it is not long enough. And to avoid assumptions about the quantity {} , I wanted to use a generator. That's what I meant:

 def tup(*args): for s in itertools.chain(args, itertools.repeat('-')): yield s print "{}, {}, {}".format(*tup(1,2)) 
Expected

:

 '1, 2, -' 

But he never returns. Can you make it work with generators? Is there a better approach?

+6
source share
4 answers

If you think about it, in addition to the fact that unpacking variable arguments is unpacked immediately, there is the fact that format does not necessarily accept its arguments in the order, as in '{2} {1} {0}' .

You can get around this if format just took the sequence, rather than requiring separate arguments, by building a sequence that does the right thing. Here's a trivial example:

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

Of course, your real version will wrap an arbitrary iterable, not a subclass of list , and you may have to use tee or the internal cache and pull out new values โ€‹โ€‹on request, only by default when you get past the end. (You might want to find the โ€œlazy listโ€ or โ€œlazy sequenceโ€ recipes in ActiveState because there are some that do this.) But that's enough to show this example.

Now how does this help us? This is not true; *lst on a DefaultList will simply try to make a tuple of the thing, giving us exactly the same number of arguments that we already had. But what if you had a version of format that could just take a sequence of arguments instead? Then you can just pass your DefaultList and it will work.

And you have this: Formatter.vformat .

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

However, there is an even simpler way if you use Formatter explicitly instead of implicitly using the str method. You can simply override its get_value method and / or its 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], {})) 

Of course, you still have to wrap your iterator in what the Sequence protocol provides.


While we are in this, your problem can be solved more directly if the language has a "iterable unpacking" protocol. See here for a python stream of ideas suggesting such a thing and all the problems that this idea has. (Also note that the format function would make this more complicated, because it would have to use the decompression protocol directly, rather than relying on the interpreter to do it magically. But assuming this is the case, then you just need to write a very simple and universal wrapper around any iterable that handles __unpack__ for it.)

+3
source

You cannot use infinite generators to populate any call to *args arbitrary arguments.

Python iterates over the generator to load all the arguments that need to be passed to the caller, and if the generator is infinite, it will never end.

You can use contactless generators without any problems. You can use itertools.islice() to close the generator:

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

In the end, you already know how many slots your template has.

+4
source

Martijn Pieters has an immediate answer, but if you want to create some kind of general wrapper / helper for autoload format , you can look at string.Formatter.parse . Using this, you can get an idea of โ€‹โ€‹how format sees the format string and highlights the count / named argument names to dynamically determine how long your iterator should be.

+3
source

A naive approach would be to provide L / 2 arguments to the format function, where L is the length of the format string. Since the replacement token has a length of at least 2 characters, you must have enough values โ€‹โ€‹to unpack it:

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

As Silas Ray suggested, a more accurate upper bound can be found using string.Formatter.parse

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

Source: https://habr.com/ru/post/952613/


All Articles