FAQ
Consider the following common exception handling idiom:

def func(iterable):
it = iter(iterable)
try:
x = next(it)
except StopIteration:
raise ValueError("can't process empty iterable")
print(x)

The intention is:

* detect an empty iterator by catching StopIteration;
* if the iterator is empty, raise a ValueError;
* otherwise process the iterator.

Note that StopIteration is an internal detail of no relevance whatsoever
to the caller. Expose this is unnecessary at best and confusing at worst.


In Python 2.6 this idiom works as intended:
func([])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in func
ValueError: can't process empty iterable

There is no sign of the StopIteration, and nor should there be.

But Python 3.1 changes this behaviour, exposing the unimportant
StopIteration and leading to a more complicated and confusing traceback:
func([])
Traceback (most recent call last):
File "<stdin>", line 4, in func
StopIteration

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in func
ValueError: can't process empty iterable


I understand the rational of this approach -- it is to assist in
debugging code where the except block is buggy and raises an error. But a
deliberate and explicit call to raise is not a buggy except block. It is
terribly inappropriate for the common use-case of catching one exception
and substituting another.

I note that the PEP explicitly notes this use-case, but merely sweeps it
under the carpet:

Open Issue: Suppressing Context
As written, this PEP makes it impossible to suppress '__context__',
since setting exc.__context__ to None in an 'except' or 'finally'
clause will only result in it being set again when exc is raised.

http://www.python.org/dev/peps/pep-3134/


Apart from this horrible idiom:

def func(iterable):
it = iter(iterable)
failed = False
try:
x = next(it)
except StopIteration:
failed = True
if failed:
raise ValueError("can't process empty iterable")
print(x)


or similar, is there really no way to avoid these chained exceptions?



--
Steven

Search Discussions

  • Hrvoje Niksic at Dec 3, 2010 at 2:15 pm

    Steven D'Aprano <steve+comp.lang.python at pearwood.info> writes:

    Consider the following common exception handling idiom:

    def func(iterable):
    it = iter(iterable)
    try:
    x = next(it)
    except StopIteration:
    raise ValueError("can't process empty iterable")
    print(x)
    Not exactly what you're looking for, but another way to express the
    above is:

    def func(iterable):
    for x in iterable:
    break
    else:
    raise ValueError("can't process empty iterable")
    print(x)

    Otherwise, I completely agree that being unable to completely replace
    the original exception is an annoyance at best.
  • Peter Otten at Dec 3, 2010 at 2:42 pm

    Steven D'Aprano wrote:

    Consider the following common exception handling idiom:

    def func(iterable):
    it = iter(iterable)
    try:
    x = next(it)
    except StopIteration:
    raise ValueError("can't process empty iterable")
    print(x)

    The intention is:

    * detect an empty iterator by catching StopIteration;
    * if the iterator is empty, raise a ValueError;
    * otherwise process the iterator.

    Note that StopIteration is an internal detail of no relevance whatsoever
    to the caller. Expose this is unnecessary at best and confusing at worst.
    http://mail.python.org/pipermail/python-list/2010-October/1258606.html
    http://mail.python.org/pipermail/python-list/2010-October/1259024.html
  • Hrvoje Niksic at Dec 3, 2010 at 3:26 pm

    Peter Otten <__peter__ at web.de> writes:

    Note that StopIteration is an internal detail of no relevance whatsoever
    to the caller. Expose this is unnecessary at best and confusing at worst.
    http://mail.python.org/pipermail/python-list/2010-October/1258606.html
    http://mail.python.org/pipermail/python-list/2010-October/1259024.html
    Both of these involve suppressing the chaining at the wrong place,
    namely in the outer handler or, worse yet, in the exception display
    mechanism. Steven, on the other hand, wants his *inner* handler to
    express that the original exception was an implementation detail, a
    business exception such as StopIteration, that is completely irrelevant
    to the actual exception being raised. The outer handler is the wrong
    place to suppress the chaining because it has no way of distinguishing
    Steven's case from a genuine case of a new exception unexpectedly
    occurring during handling of the original exception.

    One solution would be for "raise" inside except to not use the context.
    For example:

    try:
    {}[1]
    except KeyError:
    1/0

    would behave as before, but:

    But:

    try:
    {}[1]
    except KeyError:
    raise Exception("my error")

    ...would raise the custom error forgetting the KeyError.
  • Peter Otten at Dec 3, 2010 at 4:08 pm

    Hrvoje Niksic wrote:

    Peter Otten <__peter__ at web.de> writes:
    Note that StopIteration is an internal detail of no relevance whatsoever
    to the caller. Expose this is unnecessary at best and confusing at
    worst.
    http://mail.python.org/pipermail/python-list/2010-October/1258606.html
    http://mail.python.org/pipermail/python-list/2010-October/1259024.html
    Both of these involve suppressing the chaining at the wrong place,
    namely in the outer handler or, worse yet, in the exception display
    mechanism. Steven, on the other hand, wants his *inner* handler to
    express that the original exception was an implementation detail, a
    business exception such as StopIteration, that is completely irrelevant
    to the actual exception being raised. The outer handler is the wrong
    place to suppress the chaining because it has no way of distinguishing
    Steven's case from a genuine case of a new exception unexpectedly
    occurring during handling of the original exception.
    To quote the Rolling Stones: You can't always get what you want.

    After rereading the original post I still don't get why the workarounds
    provided in those links aren't worth considering.

    Peter
  • Ethan Furman at Dec 3, 2010 at 8:27 pm

    Peter Otten wrote:
    Hrvoje Niksic wrote:
    Peter Otten <__peter__ at web.de> writes:
    Note that StopIteration is an internal detail of no relevance whatsoever
    to the caller. Expose this is unnecessary at best and confusing at
    worst.
    http://mail.python.org/pipermail/python-list/2010-October/1258606.html
    http://mail.python.org/pipermail/python-list/2010-October/1259024.html
    Both of these involve suppressing the chaining at the wrong place,
    namely in the outer handler or, worse yet, in the exception display
    mechanism. Steven, on the other hand, wants his *inner* handler to
    express that the original exception was an implementation detail, a
    business exception such as StopIteration, that is completely irrelevant
    to the actual exception being raised. The outer handler is the wrong
    place to suppress the chaining because it has no way of distinguishing
    Steven's case from a genuine case of a new exception unexpectedly
    occurring during handling of the original exception.
    To quote the Rolling Stones: You can't always get what you want.

    After rereading the original post I still don't get why the workarounds
    provided in those links aren't worth considering.
    For me at least it's a matter of simplicity, clarity, and the Way of the
    Python ;)

    The workarounds are boiler-plate for a fairly common situation, and one
    of the things i _love_ about python is the *lack* of boilerplate.

    I think the real question is is there any progress on dealing with the
    Open Issue in the PEP?

    Open Issue: Suppressing Context
    As written, this PEP makes it impossible to suppress '__context__',
    since setting exc.__context__ to None in an 'except' or 'finally'
    clause will only result in it being set again when exc is raised.

    http://www.python.org/dev/peps/pep-3134/

    ~Ethan~
  • Steven D'Aprano at Dec 4, 2010 at 12:52 am

    On Fri, 03 Dec 2010 17:08:38 +0100, Peter Otten wrote:


    After rereading the original post I still don't get why the workarounds
    provided in those links aren't worth considering.

    The first work-around:

    http://mail.python.org/pipermail/python-list/2010-October/1258606.html

    is unsuitable because it requires the caller to install a custom
    excepthook. It would be rude and unacceptable for arbitrary functions to
    install hooks, possibly stomping all over the caller's own custom
    excepthook. And even if I did, or the caller did, it has the unfortunate
    side-effect of suppressing the display of *all* chained exceptions,
    including those that come from the bugs in exception handlers.


    The second work-around might be worth considering:

    http://mail.python.org/pipermail/python-list/2010-October/1259024.html

    however it adds unnecessary boilerplate to what should be a simple
    try...except...raise block, it obscures the intention of the code. As a
    work-around, it might be worth considering, but it's hardly elegant and
    it could very well be a fluke of the implementation rather than a
    guaranteed promise of the language.


    In the absence of a supported way to suppress exception chaining, I'm
    leaning towards my original work-around: set a flag in the except block,
    then raise the exception once I leave the block.

    But thanks again for the links.


    --
    Steven
  • Steven D'Aprano at Dec 4, 2010 at 12:42 am

    On Fri, 03 Dec 2010 16:26:19 +0100, Hrvoje Niksic wrote:

    Peter Otten <__peter__ at web.de> writes:
    Note that StopIteration is an internal detail of no relevance
    whatsoever to the caller. Expose this is unnecessary at best and
    confusing at worst.
    http://mail.python.org/pipermail/python-list/2010-October/1258606.html
    http://mail.python.org/pipermail/python-list/2010-October/1259024.html
    Thanks for the links Peter.

    Both of these involve suppressing the chaining at the wrong place,
    namely in the outer handler or, worse yet, in the exception display
    mechanism. Steven, on the other hand, wants his *inner* handler to
    express that the original exception was an implementation detail, a
    business exception such as StopIteration, that is completely irrelevant
    to the actual exception being raised.
    Yes, exactly! Python 3.x exposes completely irrelevant and internal
    details in the traceback.

    The outer handler is the wrong
    place to suppress the chaining because it has no way of distinguishing
    Steven's case from a genuine case of a new exception unexpectedly
    occurring during handling of the original exception.

    One solution would be for "raise" inside except to not use the context.
    I would have thought that was such an obvious solution that I was
    gobsmacked to discover the PEP 3134 hadn't already considered it. If you
    *explicitly* raise an exception inside an exception handler, surely it's
    because you want to suppress the previous exception as an internal detail?

    If not, and you want to chain it with the previous exception, the
    solution is simple, obvious and straight-forward: explicit chaining.

    try:
    something()
    except SomeException as exc:
    raise MyException from exc



    For example:

    try:
    {}[1]
    except KeyError:
    1/0

    would behave as before, but:

    Yes, that presumably would be a bug and should chain exceptions.

    But:

    try:
    {}[1]
    except KeyError:
    raise Exception("my error")

    ...would raise the custom error forgetting the KeyError.

    That's exactly the behaviour I would expect and I'm surprised that this
    feature was put into production without some simple way to support this
    idiom.



    --
    Steven
  • Ethan Furman at Dec 3, 2010 at 8:15 pm

    I found #6210 on bugs.python.org -- does anyone know if there are any
    others regarding this issue? Or any progress on MRAB's idea?

    MRAB wrote:
    Suggestion: an explicit 'raise' in the exception handler excludes the
    context, but if you want to include it then 'raise with'. For example: >
    # Exclude the context
    try:
    command_dict[command]()
    except KeyError:
    raise CommandError("Unknown command") >
    # Include the context
    try:
    command_dict[command]()
    except KeyError:
    raise with CommandError("Unknown command")
    ~Ethan~
  • Paul Rubin at Dec 3, 2010 at 6:15 pm

    Steven D'Aprano <steve+comp.lang.python at pearwood.info> writes:
    def func(iterable):
    it = iter(iterable)
    failed = False
    try:
    x = next(it)
    except StopIteration:
    failed = True
    if failed:
    raise ValueError("can't process empty iterable")
    print(x)
    Untested:

    from itertools import islice

    def func(iterable):
    xs = list(islice(iter(iterable), 1))
    if len(xs) == 0:
    raise ValueError(...)
    print xs[0]
  • Steven D'Aprano at Dec 3, 2010 at 11:28 pm

    On Fri, 03 Dec 2010 10:15:58 -0800, Paul Rubin wrote:

    Steven D'Aprano <steve+comp.lang.python at pearwood.info> writes:
    def func(iterable):
    it = iter(iterable)
    failed = False
    try:
    x = next(it)
    except StopIteration:
    failed = True
    if failed:
    raise ValueError("can't process empty iterable")
    print(x)
    Untested:

    from itertools import islice

    def func(iterable):
    xs = list(islice(iter(iterable), 1))
    if len(xs) == 0:
    raise ValueError(...)
    print xs[0]

    If you're intention was to make me feel better about the version above
    that sets a flag, you succeeded admirably!

    :)


    --
    Steven
  • John Nagle at Dec 6, 2010 at 8:53 pm

    On 12/3/2010 5:04 AM, Steven D'Aprano wrote:
    Consider the following common exception handling idiom:

    def func(iterable):
    it = iter(iterable)
    try:
    x = next(it)
    except StopIteration:
    raise ValueError("can't process empty iterable")
    print(x)

    The intention is:

    * detect an empty iterator by catching StopIteration;
    * if the iterator is empty, raise a ValueError;
    * otherwise process the iterator.

    Note that StopIteration is an internal detail of no relevance whatsoever
    to the caller. Expose this is unnecessary at best and confusing at worst.
    Right. You're not entitled to assume that StopIteration is
    how a generator exits. That's a CPyton thing; generators were
    a retrofit, and that's how they were hacked in. Other implementations
    may do generators differently.

    John Nagle
  • Mark Wooding at Dec 6, 2010 at 10:24 pm

    John Nagle <nagle at animats.com> writes:

    Right. You're not entitled to assume that StopIteration is how a
    generator exits. That's a CPyton thing; generators were a retrofit,
    and that's how they were hacked in. Other implementations may do
    generators differently.
    This is simply wrong. The StopIteration exception is a clear part of
    the generator protocol as described in 5.2.8 of the language reference;
    the language reference also refers to 3.5 of the library reference,
    which describes the iterator protocol (note, not the generator
    implementation -- all iterators work the same way), and explicitly
    mentions StopIteration as part of the protocol.

    -- [mdw]
  • John Nagle at Dec 7, 2010 at 4:58 am

    On 12/6/2010 2:24 PM, Mark Wooding wrote:
    John Nagle<nagle at animats.com> writes:
    Right. You're not entitled to assume that StopIteration is how a
    generator exits. That's a CPyton thing; generators were a retrofit,
    and that's how they were hacked in. Other implementations may do
    generators differently.
    This is simply wrong. The StopIteration exception is a clear part of
    the generator protocol as described in 5.2.8 of the language reference;
    the language reference also refers to 3.5 of the library reference,
    which describes the iterator protocol (note, not the generator
    implementation -- all iterators work the same way), and explicitly
    mentions StopIteration as part of the protocol.

    -- [mdw]
    PEP 255, like too much Python literature, doesn't distinguish clearly
    between the language definition and implementation detail. It says
    "The mechanics of StopIteration are low-level details, much like the
    mechanics of IndexError in Python 2.1". Applications shouldn't be
    explicitly using StopIteration.

    IronPython doesn't do StopIteration the same way CPython does.

    http://ironpython.codeplex.com/wikipage?title=IPy1.0.xCPyDifferences

    Neither does Shed Skin.

    John Nagle
  • Mark Wooding at Dec 7, 2010 at 10:00 am

    John Nagle <nagle at animats.com> writes:

    PEP 255, like too much Python literature, doesn't distinguish
    clearly between the language definition and implementation detail. It
    says "The mechanics of StopIteration are low-level details, much like
    the mechanics of IndexError in Python 2.1". Applications shouldn't be
    explicitly using StopIteration.
    You've twisted the words by quoting them out of context, and have
    attempted to force a misinterpretation of `low-level details' as
    `implementation detail'.

    That text comes from a question-and-answer section, in response to the
    question `why not force termination to be spelled "StopIteration"?'.
    This is a fine answer to the question: the details of the (preexisting
    -- see PEP 234) iteration protocol are abstracted by the generator
    syntax. But it doesn't at all mean that the StopIteration exception
    isn't an official, use-visible part of Python.
    IronPython doesn't do StopIteration the same way CPython does.

    http://ironpython.codeplex.com/wikipage?title=IPy1.0.xCPyDifferences
    IronPython's behaviour when you try to fetch items from a spent
    generator is different. It still implements the same iterator protocol,
    and raises StopIteration when it has no more items to yield.

    You're not stupid, but you'd have to be in order to think that these
    references support your claim that
    You're not entitled to assume that StopIteration is how a generator
    exits. That's a CPyton thing; generators were a retrofit, and
    that's how they were hacked in. Other implementations may do
    generators differently.
    I don't want to conclude that you're not arguing in good faith but I'm
    not seeing many other possibilities.

    -- [mdw]
  • Steve Holden at Dec 7, 2010 at 11:56 pm

    On 12/7/2010 5:58 AM, John Nagle wrote:
    PEP 255, like too much Python literature, doesn't distinguish clearly
    between the language definition and implementation detail. It says
    "The mechanics of StopIteration are low-level details, much like the
    mechanics of IndexError in Python 2.1". Applications shouldn't be
    explicitly using StopIteration.
    So you don't think that we should rely on iterables with no __iter__()
    method to raise IndexError to terminate iterations when their
    __getitem__() is called with an invalid index? The IndexError mechanism
    was, to the best of my less-than-complete knowledge, used by all pre-2.2
    implementations. The quoted paragraph appears to be intended to reassure
    the applications programmer that there is no normal need to handle
    StopIteration specially - just as there was no need to handle IndexError
    specially.
    IronPython doesn't do StopIteration the same way CPython does.

    http://ironpython.codeplex.com/wikipage?title=IPy1.0.xCPyDifferences
    Perhaps not, but the only difference is what happens on repeated calls
    to next() after the iterator is exhausted. The iterator still terminates
    by raising a StopIteration error.

    I have no idea what Shed Skin does, but to the extent that iterators
    don't raise StopIteration on exhaustion I'd say it is in error.

    regards
    Steve
    --
    Steve Holden +1 571 484 6266 +1 800 494 3119
    PyCon 2011 Atlanta March 9-17 http://us.pycon.org/
    See Python Video! http://python.mirocommunity.org/
    Holden Web LLC http://www.holdenweb.com/
  • Paul Rubin at Dec 6, 2010 at 9:13 pm

    Steven D'Aprano <steve+comp.lang.python at pearwood.info> writes:
    Apart from this horrible idiom:

    def func(iterable):
    it = iter(iterable)
    failed = False
    try:
    x = next(it)
    except StopIteration:
    failed = True
    if failed:
    raise ValueError("can't process empty iterable")
    print(x)


    or similar, is there really no way to avoid these chained exceptions?
    Seems like yet another example of people doing messy things with
    exceptions that can easily be done with iterators and itertools:

    from itertools import islice

    def func(iterable):
    xs = list(islice(iter(iterable), 1))
    if len(xs) == 0:
    raise ValueError(...)
    print xs[0]

    It's really unfortunate, though, that Python 3 didn't offer a way to
    peek at the next element of an iterable and test emptiness directly.
  • Steven D'Aprano at Dec 7, 2010 at 12:23 am

    On Mon, 06 Dec 2010 13:13:40 -0800, Paul Rubin wrote:

    It's really unfortunate, though, that Python 3 didn't offer a way to
    peek at the next element of an iterable and test emptiness directly.
    This idea of peekable iterables just won't die, despite the obvious flaws
    in the idea.

    There's no general way of telling whether or not a lazy sequence is done
    except to actually generate the next value, and caching that value is not
    appropriate for all such sequences since it could depend on factors which
    have changed between the call to peek and the call to next.

    If you want to implement a peek method in your own iterables, go right
    ahead. But you can't make arbitrary iterables peekable without making a
    significant class of them buggy.



    --
    Steven
  • MRAB at Dec 7, 2010 at 12:48 am

    On 07/12/2010 00:23, Steven D'Aprano wrote:
    On Mon, 06 Dec 2010 13:13:40 -0800, Paul Rubin wrote:

    It's really unfortunate, though, that Python 3 didn't offer a way to
    peek at the next element of an iterable and test emptiness directly.
    This idea of peekable iterables just won't die, despite the obvious flaws
    in the idea.

    There's no general way of telling whether or not a lazy sequence is done
    except to actually generate the next value, and caching that value is not
    appropriate for all such sequences since it could depend on factors which
    have changed between the call to peek and the call to next.

    If you want to implement a peek method in your own iterables, go right
    ahead. But you can't make arbitrary iterables peekable without making a
    significant class of them buggy.
    Perhaps Python could use Guido's time machine to check whether the
    sequence will yield another object in the future. :-)
  • Steve Holden at Dec 7, 2010 at 11:58 pm

    On 12/7/2010 1:48 AM, MRAB wrote:
    Perhaps Python could use Guido's time machine to check whether the
    sequence will yield another object in the future. :-)
    Since there's only one time machine that would effectively be a lock
    across all Python interpreters.

    regards
    Steve
    --
    Steve Holden +1 571 484 6266 +1 800 494 3119
    PyCon 2011 Atlanta March 9-17 http://us.pycon.org/
    See Python Video! http://python.mirocommunity.org/
    Holden Web LLC http://www.holdenweb.com/
  • John Nagle at Dec 7, 2010 at 5:05 am

    On 12/6/2010 4:23 PM, Steven D'Aprano wrote:
    On Mon, 06 Dec 2010 13:13:40 -0800, Paul Rubin wrote:

    It's really unfortunate, though, that Python 3 didn't offer a way to
    peek at the next element of an iterable and test emptiness directly.
    This idea of peekable iterables just won't die, despite the obvious flaws
    in the idea.

    There's no general way of telling whether or not a lazy sequence is done
    except to actually generate the next value, and caching that value is not
    appropriate for all such sequences since it could depend on factors which
    have changed between the call to peek and the call to next.
    Right.

    Pascal had the predicates "eoln(file)" and "eof(file)", which
    were tests for end of line and end of file made before reading. This
    caused much grief with interactive input, because the test would
    stall waiting for the user to type something. Wirth originally
    intended Pascal for batch jobs, and his version didn't translate
    well to interactive use. (Wirth fell in love with his original
    recursive-descent compiler, which was simple but limited. He
    hated to have language features that didn't fit his compiler model
    well. This held the language back and eventually killed it.)

    C I/O returned a unique value on EOF, but there was no way to
    test for it before reading. Works much better. The same issues apply
    to pipes, sockets, qeueues, interprocess communication, etc.

    John Nagle
  • Arnaud Delobelle at Dec 13, 2010 at 7:09 pm

    Paul Rubin <no.email at nospam.invalid> writes:

    Steven D'Aprano <steve+comp.lang.python at pearwood.info> writes:
    Apart from this horrible idiom:

    def func(iterable):
    it = iter(iterable)
    failed = False
    try:
    x = next(it)
    except StopIteration:
    failed = True
    if failed:
    raise ValueError("can't process empty iterable")
    print(x)


    or similar, is there really no way to avoid these chained exceptions?
    Seems like yet another example of people doing messy things with
    exceptions that can easily be done with iterators and itertools:

    from itertools import islice

    def func(iterable):
    xs = list(islice(iter(iterable), 1))
    if len(xs) == 0:
    raise ValueError(...)
    print xs[0]

    It's really unfortunate, though, that Python 3 didn't offer a way to
    peek at the next element of an iterable and test emptiness directly.
    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    Or using 3.x's next's optional second argument:

    _nonext=object()
    def func(iterable):
    x = next(iter(iterable), _nonext)
    if x is _nonext:
    raise ValueError("... empty iterable")
    print(x)

    --
    Arnaud
  • Rob Richardson at Dec 13, 2010 at 7:56 pm
    Arnaud,

    Wouldn't your first suggestion exit after the first element in iterable?
    And would your second suggestion throw an exception after normal
    processing of all elements in the interator?

    RobR

    -----Original Message-----


    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    Or using 3.x's next's optional second argument:

    _nonext=object()
    def func(iterable):
    x = next(iter(iterable), _nonext)
    if x is _nonext:
    raise ValueError("... empty iterable")
    print(x)
  • Ethan Furman at Dec 13, 2010 at 8:44 pm
    Please don't top-post.

    Rob Richardson wrote:
    -----Original Message-----


    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    Or using 3.x's next's optional second argument:

    _nonext=object()
    def func(iterable):
    x = next(iter(iterable), _nonext)
    if x is _nonext:
    raise ValueError("... empty iterable")
    print(x)
    Arnaud, >
    Wouldn't your first suggestion exit after the first element in
    iterable?
    No, it hit's return instead.
    And would your second suggestion throw an exception after normal
    processing of all elements in the interator?
    Looks like the second solution doesn't process the entire iterable, just
    it's first element.

    ~Ethan~
  • Ethan Furman at Dec 13, 2010 at 9:19 pm

    Ethan Furman wrote:
    Please don't top-post.

    Rob Richardson wrote:
    -----Original Message-----


    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    Or using 3.x's next's optional second argument:

    _nonext=object()
    def func(iterable):
    x = next(iter(iterable), _nonext)
    if x is _nonext:
    raise ValueError("... empty iterable")
    print(x)
    Arnaud,

    Wouldn't your first suggestion exit after the first element in
    iterable?
    No, it hit's return instead.
    Doh -- Yes, it does.

    It seems both solutions only get the first element, not all elements in
    the iterator...

    Maybe this instead:

    def func(iterable):
    for x in iterable:
    break
    else:
    raise ValueError("... empty iterable")
    for xx in chain((x, ), iterable):
    process(xx)

    Can't say as I care for this -- better to fix the unwanted nesting in
    the tracebacks from raise.

    ~Ethan~
  • Ethan Furman at Dec 13, 2010 at 8:46 pm

    Arnaud Delobelle wrote:
    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    For the immediate case this is a cool solution.

    Unfortunately, it doesn't fix the unwanted nesting of exceptions problem.

    ~Ethan~
  • Ethan Furman at Dec 13, 2010 at 9:23 pm

    Ethan Furman wrote:
    Arnaud Delobelle wrote:
    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    For the immediate case this is a cool solution.

    Drat -- I have to take that back -- the OP stated:
    The intention is: >
    * detect an empty iterator by catching StopIteration;
    * if the iterator is empty, raise a ValueError;
    * otherwise process the iterator.

    Presumably, the print(x) would be replaced with code that processed the
    entire iterable (including x, of course), and not just its first element.

    ~Ethan~
  • Arnaud Delobelle at Dec 13, 2010 at 9:43 pm

    Ethan Furman <ethan at stoneleaf.us> writes:

    Ethan Furman wrote:
    Arnaud Delobelle wrote:
    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    For the immediate case this is a cool solution.

    Drat -- I have to take that back -- the OP stated:
    The intention is:

    * detect an empty iterator by catching StopIteration;
    * if the iterator is empty, raise a ValueError;
    * otherwise process the iterator.

    Presumably, the print(x) would be replaced with code that processed
    the entire iterable (including x, of course), and not just its first
    element.
    As I had stated before, I didn't where the discussion started from. I
    replied to code posted by Steven D'Aprano and Paul Rubin. My code
    snippet was equivalent in functionality to theirs, only a little
    simpler.

    Now if one wants to raise an exception if an iterator is empty, else
    process it somehow, it must mean that the iterator needs to have at
    least one element for the processing to be meaningful and so it can be
    thought of as a function of one element and of one iterator:

    process(first, others)

    which never needs to raise an exception (at least related to the number
    of items in the iterator). Therefore you can write your function as
    follows:

    def func(iterable):
    iterator = iter(iterable)
    for first in iterable:
    return process(first, iterator)
    else:
    raise ValueError("need non-empty iterable")

    --
    Arnaud
  • Arnaud Delobelle at Dec 13, 2010 at 8:39 pm
    "Rob Richardson" <Rob.Richardson at rad-con.com> writes:

    You shouldn't top-post!
    Arnaud,

    Wouldn't your first suggestion exit after the first element in iterable?
    Yes, after printing that element, which is what the code I quoted did.
    And would your second suggestion throw an exception after normal
    processing of all elements in the interator?
    No. It would have the same behaviour as the first one.
    RobR

    -----Original Message-----


    I missed the start of this discussion but there are two simpler ways:

    def func(iterable):
    for x in iterable:
    print(x)
    return
    raise ValueError("... empty iterable")

    Or using 3.x's next's optional second argument:

    _nonext=object()
    def func(iterable):
    x = next(iter(iterable), _nonext)
    if x is _nonext:
    raise ValueError("... empty iterable")
    print(x)

    --
    Arnaud
    --
    Arnaud

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouppython-list @
categoriespython
postedDec 3, '10 at 1:04p
activeDec 13, '10 at 9:43p
posts29
users11
websitepython.org

People

Translate

site design / logo © 2022 Grokbase