On Wed, Sep 23, 2020 at 3:52 AM Stavros Macrakis <macra...@alum.mit.edu> wrote: > > Thanks to everyone for the comments, especially Tim Chase for the simple > and elegant tuple unpacking solution, and Léo El Amri for the detailed > comments on the variants. Below are some more variants which *don't *use > tuple unpacking, on the theory that the coding patterns may be useful in > other cases where unpacking doesn't apply.
When doesn't it apply? Can you elaborate on this? It might be easier to advise on Pythonic style when the specific requirements are known. > For me, one of the interesting lessons from all these finger exercises is > that *for* and unpacking hide a lot of messiness, both the initial *iter* call > and the exception handling. I don't see any way to eliminate the *iter*, > but there are ways to avoid the verbose exception handling. In Python, exception handling IS the way to do these things. Having a two-part return value rather than using an exception is an unusual idiom in Python (although it's well known in other languages; I believe JavaScript does iterators this way, for one). > Using the second arg to *next*, we get what is arguably a more elegant > solution: > > > _uniq = [] > def firstg(iterable): > it = iter(iterable) > val0 = next(it,_uniq) > val1 = next(it,_uniq) > if val0 is not _uniq and val1 is _uniq: > return val0 > else: > raise ValueError("first1: arg not exactly 1 long") > > But I don't know if the *_uniq* technique is considered Pythonic. It is when it's needed, but a more common way to write this would be to have the sentinel be local to the function (since it doesn't need to be an argument): def firstg_variant(iterable): it = iter(iterable) sentinel = object() first = next(it, sentinel) if first is sentinel: raise ValueError("empty iterable") second = next(it, sentinel) if second is not sentinel: raise ValueError("too many values") return first But getting a return value and immediately checking it is far better spelled "try/except" here. (Note, BTW, that I made a subtle change to the logic here: this version doesn't call next() a second time if the first one returned the sentinel. This avoids problems with broken iterators that raise StopException and then keep going.) > If *next* were instead defined to return a flag (rather than raising an > exception), the code becomes cleaner and clearer, something like this: > > > def firsth(iterable): > it = iter(iterable) > (val0, good0) = next2(it) > (val1, good1) = next2(it) # val1 is dummy > if good0 and not good1: > return val0 > else: > raise ValueError("first1: arg not exactly 1 long") > IMO this isn't any better than the previous one. You still need a sentinel, but now you use True and False instead of a special object. It isn't *terrible*, but it's no advantage either. ChrisA -- https://mail.python.org/mailman/listinfo/python-list