"for" with "else"?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • John Roth

    #16
    Re: "for&qu ot; with "else&quot ;?


    "Andrew Dalke" <adalke@mindspr ing.com> wrote in message
    news:f%reb.1079 7$NX3.1826@news read3.news.pas. earthlink.net.. .[color=blue]
    > John Roth:[color=green]
    > > I don't see what exceptions have to do with it. There are three
    > > special ending cases: with break, empty sequence and sequence
    > > exhausted. Empty sequence is the only one that isn't handled easily.[/color]
    >
    > Could you give an example of when you would actually use an
    > 'empty_sequence ' in the real world? It's never been a problem
    > for me (that I can recall) where that distinction was important.[/color]

    I can recall writing a fair number of loops a while back where
    knowing that I had an empty sequence was important. More
    recently, I have concentrated on making things simply work
    if a sequence is empty. I think that's much better programming
    practice when you can accomplish it. However, the days when
    I thought I was Joe Superprogrammer are long in my past,
    and while I don't have a current example, I've got enough
    examples from my past that I can't say I could eliminate all
    of them.

    One example that comes to mind is the case where you
    don't want to send a message at all if there are no instances;
    for example, if you are doing a build, the case of no failures
    is distinct from the case that there were failures. You want to
    do two totally different things. Add the wrinkle that it may not
    be easy, convenient or possible to test for an empty sequence
    first (think some forms of generator) and I think you can come
    up with a number of cases.


    [color=blue][color=green]
    > > What I'd like to see is a more general exit clause that also
    > > applies to the while statement. Something like:
    > >
    > > on break:
    > > on normal_exit:
    > > on empty_sequence:
    > > on body_not_execut ed:[/color]
    >
    > Aren't 'empty_sequence ' and 'body_not_execu ted' identical?[/color]

    Yes, they are. One is more expressive with the for statement,
    and the other with the while statement.
    [color=blue]
    >
    > Could you give examples using each one?
    >
    > What about code which uses a return in the block? Is
    > that counted under 'break'?[/color]

    I hadn't thought about it. I'd consider it a break.
    [color=blue]
    > What about raising an exception?[/color]

    That should be handled with a try / except block.
    I don't see any reason for new syntax.

    John Roth
    [color=blue]
    >
    > Andrew
    > dalke@dalkescie ntific.com
    >
    >[/color]


    Comment

    • Stephen Horne

      #17
      Re: &quot;for&qu ot; with &quot;else&quot ;?

      On Wed, 01 Oct 2003 03:38:51 GMT, "Andrew Dalke"
      <adalke@mindspr ing.com> wrote:
      [color=blue]
      >John Roth:[color=green]
      >> I don't see what exceptions have to do with it. There are three
      >> special ending cases: with break, empty sequence and sequence
      >> exhausted. Empty sequence is the only one that isn't handled easily.[/color]
      >
      >Could you give an example of when you would actually use an
      >'empty_sequenc e' in the real world? It's never been a problem
      >for me (that I can recall) where that distinction was important.[/color]

      It's not so much for 'for' loops as for 'while' loops that I've hit
      it. Actually, it came up again in some code for the 'primes' thread.

      I thought I'd try a tweak of the seive. Whenever I found a new prime,
      I'd add a tuple (2*prime, prime) to a queue of non-prime values. It
      seemed a nice test of an ordered set class I have written as an
      extension.

      While searching for new primes, a gap in the queue indicates a new
      prime has been found. So the code became...

      def Primes (p_Max) :
      l_Primes = [2]
      l_Rejects = c_Multiset () # tuples of (value, prime factor)
      l_Cursor = c_Multiset_Curs or ()
      i = 3

      l_Rejects.Inser t ( (4, 2) ) # prime rejects

      l_Cursor.Find_F irst (l_Rejects)

      while i <= p_Max :
      if l_Cursor.Key [0] <= i :
      while l_Cursor.Key [0] <= i :
      l_Value, l_Factor = l_Cursor.Key
      l_Value += l_Factor

      if l_Value <= p_Max :
      l_Rejects.Inser t ( (l_Value, l_Factor) )

      l_Cursor.Del_St ep_Next ()

      i += 1

      else :
      l_Primes.append (i)
      l_Rejects.Inser t ( (i*2, i) )
      i += 1

      return l_Primes


      The annoyance is the duplication of cases here...

      if l_Cursor.Key [0] <= i :
      while l_Cursor.Key [0] <= i :

      If non-running-the-body-even-once was considered the 'else' special
      case, the duplication wouldn't be needed.


      As soon as I read Michael Gearys post I remembered the
      not-found-anything-in-search application, which does make some sense.

      : for item in allitems:
      : if fraboozable(ite m):
      : print "first fraboozable item is", item
      : break
      : else:
      : print "Sorry, no item is fraboozable"
      :

      Personally I tend to avoid 'break' as far as reasonable, seeing it as
      unstructured - a kind of goto. As I wouldn't usually use break that
      way, I equally wouldn't use else that way either.

      In this case, I would make use of one of my first generators...

      def linearsearch (p_Pred, p_Seq) :
      i = 0
      for j in p_Seq :
      if p_Pred (j) :
      yield (i, j)
      i += 1

      Giving code like...

      search = linearsearch (fraboozable, allitems)

      try :
      index, value = search.next ()
      print "first fraboozable item is", index

      except StopIteration :
      print "Sorry, no item is fraboozable"


      This may seem odd at first, but it has a major advantage - you can
      easily continue the search from where you left off.

      BTW - you can get the same effect with itertools.ifilt er and
      enumerate, but it's a bit of a pain.


      --
      Steve Horne

      steve at ninereeds dot fsnet dot co dot uk

      Comment

      • Alex Martelli

        #18
        Re: &quot;for&qu ot; with &quot;else&quot ;?

        Stephen Horne wrote:
        ...[color=blue]
        > As soon as I read Michael Gearys post I remembered the
        > not-found-anything-in-search application, which does make some sense.
        >
        > : for item in allitems:
        > : if fraboozable(ite m):
        > : print "first fraboozable item is", item
        > : break
        > : else:
        > : print "Sorry, no item is fraboozable"
        >
        > Personally I tend to avoid 'break' as far as reasonable, seeing it as[/color]

        Me too: it's just that I think that most attempts to avoid break are
        totally unreasonable, so I don't do them:-).
        [color=blue]
        > unstructured - a kind of goto. As I wouldn't usually use break that
        > way, I equally wouldn't use else that way either.[/color]

        break is a "structured goto", no different in that respect from if/else,
        while, continue. Or the explicit try/except StopIteration that you
        appear to prefer so intensely, for that matter.
        [color=blue]
        > In this case, I would make use of one of my first generators...[/color]

        So, if "fraboozable(it em)" stood for, say, "item**3 > 33", would
        you use lambda or a named function in order to pass the "predicate"
        to your generator? I think either case is far less readable than just
        expressing the condition inline (which is part of why list comprehensions
        beat map and filter by a mile -- they're all expressed inline).
        [color=blue]
        > This may seem odd at first, but it has a major advantage - you can
        > easily continue the search from where you left off.[/color]

        If I needed that, I would simply make an explicit iterator for the
        items sequence, of course:

        itemsiter = iter(allitems)
        for item in itemsiter:
        if fraboozable(ite m):
        # and the rest as above

        thus making this loop just as "continuabl e where it left off" as
        a generator would be. Moreover, if what I need after the first
        fraboozable item is the first succeeding bambalable item, I need
        no major restructuring -- I'll just use bambalable(item ) in the
        second "continuing " loop.
        [color=blue]
        > BTW - you can get the same effect with itertools.ifilt er and
        > enumerate, but it's a bit of a pain.[/color]

        So we disagree on this sub-topic too -- I think composing itertools
        functions and such builtins as enumerate is more often a veritable
        pleasure, far more fun than writing generators more often than not.
        In this particular case, since the index within the sequence is not
        of interest, itertools.ifilt er seems clearly preferable, in particular.

        But simple for loops, _with_ their due complement of break and else
        as needed, are often more practical, anyway. Often a for loop WILL in
        fact run on the iterator returned by some sophisticated generator
        (whether that's freshly coded, or composed from itertools bricks),
        but ALSO add the small extra of a useful break just where it does
        most good.

        For example, consider checking whether a sequence is sorted or not
        (ignoring for simplicity empty sequences):

        seqiter = iter(seq)
        lastone = seqiter.next()
        for item in seqiter:
        if compare_badly(i tem, lastone):
        print "unsorted"
        break
        lastone = item
        else:
        print "sorted"

        where often but not always compare_badly will be > . Writing a very
        special-case generator just for this use would go, let's see, how?

        def check_sorted(an iter, compare_badly=N one):
        if compare_badly is None:
        def compare_badly(x , y): return x>y
        seqiter = iter(seq)
        lastone = seqiter.next()
        for item in seqiter:
        yield compare_badly(i tem, lastone)
        lastone = item

        ??? Or with a less-special-cased one such as:

        def window_by_two(a niter):
        seqiter = iter(seq)
        lastone = seqiter.next()
        for item in seqiter:
        yield item, lastone
        lastone = item

        ??? At least the latter makes sense, since it encapsulates a useful
        general concept, "windowing by 2" of a sequence. But isn't the "one
        obvious way" to use it...:

        for item, lastone in window_by_two(s eq):
        if compare_badly(i tem, lastone):
        print "unsorted"
        break
        else:
        print "sorted"

        ??? Well, I guess it is only for those of us that don't suffer from
        allergy to 'break' -- if one did, then maybe

        badpairs = itertools.ifilt er(lambda x: compare_badly(* x),
        window_by_two(s eq))
        try:
        badpairs.next()
        except StopIteration:
        print "sorted"
        else:
        print "unsorted"

        might prove "more obvious" (???-).


        Alex

        Comment

        • Stephen Horne

          #19
          Re: &quot;for&qu ot; with &quot;else&quot ;?

          On Wed, 01 Oct 2003 18:10:40 GMT, Alex Martelli <aleax@aleax.it >
          wrote:
          [color=blue]
          >break is a "structured goto", no different in that respect from if/else,
          >while, continue. Or the explicit try/except StopIteration that you
          >appear to prefer so intensely, for that matter.[/color]

          Basically, to me, a key thing about the term 'block structured' is
          about a clear and simple layout and a separation of 'layers'. The body
          contained within a block structure should be distinct from that
          structure - there shouldn't be bits of the block structure hidden
          inside the body. If 'break' is a part of the loop, it shouldn't be
          buried inside the body - it should be associated with the loop.

          It's a similar principle to encapsulation, in a way.

          That is the 'why' for the PEP315 discussion a little while back -
          though as I said then, I'm not that much against the existing 'if x :
          break' - I mainly don't like the existing proposal that means 'while x
          :' may get a new and potentially confusing meaning, so wanted to
          suggest an alternate syntax if some change is definitely happening.


          An exception is subtly different. It's certainly a matter of
          pragmatics - a raise is certainly unstructured in the sense I
          described above - but that is its whole reason for existence.

          When you see a loop, at least nine times out of ten it will not have a
          break or a continue. The expectation, therefore, is that these hidden
          exit points do not exist. When you see a try block with an exception
          handler, that is a pretty strong hint that the code in the try block
          may raise that exception - no matter how well hidden the raise may be.

          Furthermore, exceptions are named - another useful hint to what is
          going on.


          Actually, 'break' can be prone to a fairly common maintenance error.
          If you start with this...

          while x :
          ...
          if y :
          break;
          ...

          and add an inner loop, failing to notice the hidden break, like
          this...

          while x :
          ...
          while z :
          ...
          if y : break;
          ...
          ...

          ....that code will fail as the break now only exits the inner loop, but
          if you raise an exception, the code starts as...

          try :
          while x :
          ...
          if y : raise blah
          ...
          except blah :
          ...

          and becomes...

          try :
          while x :
          ...
          while z :
          ...
          if y : raise blah
          ...
          ...
          except blah :
          ...

          The key advantage being that the exception still exits the outer loop
          as well as the inner loop, so the result is still as intended even if
          the maintainer didn't notice the raise statement.

          Of course this is often overkill, which is part of why break is
          sometimes reasonable.
          [color=blue][color=green]
          >> In this case, I would make use of one of my first generators...[/color]
          >
          >So, if "fraboozable(it em)" stood for, say, "item**3 > 33", would
          >you use lambda or a named function in order to pass the "predicate"
          >to your generator? I think either case is far less readable than just
          >expressing the condition inline (which is part of why list comprehensions
          >beat map and filter by a mile -- they're all expressed inline).[/color]

          I tend to agree with you. I will use lambda without too much
          hesitation, but at the very least I wish it had a better name.

          Of course you can often define a named function just before it is
          needed. For instance...

          def example :
          ...
          def predicate (x) :
          return some-function-of (x)

          a = itertools.ifilt er (predicate, seq)


          Inline has definite advantages, and I certainly prefer list
          comprehensions to combining filter and map (and lets not forget that
          list comprehensions have some additional tricks up their sleeves) but
          neither a lambda nor a named function is really a disaster.
          [color=blue][color=green]
          >> BTW - you can get the same effect with itertools.ifilt er and
          >> enumerate, but it's a bit of a pain.[/color]
          >
          >So we disagree on this sub-topic too -- I think composing itertools
          >functions and such builtins as enumerate is more often a veritable
          >pleasure, far more fun than writing generators more often than not.
          >In this particular case, since the index within the sequence is not
          >of interest, itertools.ifilt er seems clearly preferable, in particular.[/color]

          I made a stupid mistake there - I thought the index was relevant,
          which is why I mentioned enumerate and gave my own generator. I guess
          I've got indices on the brain after the PEP288 and PEP322 discussions.

          If the enumerate was necessary, the expression needs the ifilter, the
          enumerate, and a lambda. It is the combination that can get confusing,
          especially when the resulting expression needs two or more lines.
          [color=blue]
          >seqiter = iter(seq)
          >lastone = seqiter.next()
          >for item in seqiter:
          > if compare_badly(i tem, lastone):
          > print "unsorted"
          > break
          > lastone = item
          >else:
          > print "sorted"[/color]

          This is extremely compelling, I have to admit - loops that iterates
          through sliding window positions (and thus for all items but one, or
          whatever) can be a pain at times, and this combination of iterator and
          for loop gives a very nice structure for it.

          I never thought of this idea because of my historic understanding of
          'for' as looping through the whole sequence. Thanks for helping me
          take off those blinkers.

          I'd still be tempted to say that 'if you expect it to be sorted, then
          unsorted is an error' though and raise an exception when the unsorted
          pair is found...

          try:
          seqiter = iter(seq)
          lastone = seqiter.next()
          for item in seqiter:
          if compare_badly(i tem, lastone): raise unsorted
          lastone = item

          print "sorted"

          catch unsorted :
          print "unsorted"


          But it is just a personal preference of course, and has no advantage
          at all in a trivial case like this.


          BTW...

          I didn't say I'd use a generator for everything - I said "In this
          case" (and was wrong because I misread the case - as you say, I only
          needed itertools.ifilt er).

          I also do not have an allergy to break - as I said, I "I tend to avoid
          'break' as far as reasonable" but as you said there are times when it
          is unreasonable to avoid it.

          The biggest rule I have for 'break' is that the loop body must be
          small enough that the break is unmissable. If the loop is that simple,
          however, there is usually a simple way to handle the requirement that
          doesn't need break.

          If there is a simple alternative I will tend to prefer it over using
          break. Therefore I rarely use break, and therefore I have never (as
          far as I remember) used else on a loop. That is all I am saying. I'm
          not a fundamentalist by any means.


          --
          Steve Horne

          steve at ninereeds dot fsnet dot co dot uk

          Comment

          • Stephen Horne

            #20
            Re: &quot;for&qu ot; with &quot;else&quot ;?

            On Wed, 01 Oct 2003 03:31:10 GMT, "Andrew Dalke"
            <adalke@mindspr ing.com> wrote:
            [color=blue]
            >In my search, I didn't see any examples which were
            >better done with exceptions -- and since (string) exceptions
            >existed in the language for a long time (from the start
            >I would imagine and definitely pre-1.3, which is
            >about when I started), I find it hard to believe that
            >your statement reflects what really happened.[/color]

            You are quite right - I think I mentioned already in another post that
            I'd completely forgotten the break-on-found idiom until I read Michael
            Gearys post (I don't always read a thread fully before I reply to
            stuff).

            I still think that exception-based approaches are far from painful for
            this kind of stuff. However, with your example from binhex.py...

            for c in data:
            if not c.isspace() and (c<' ' or ord(c) > 0x7f):
            break
            else:
            finfo.Type = 'TEXT'

            The cleanest way to eliminate the 'break' and 'else' is probably as
            follows...

            try:
            c = data.next ()
            while c.isspace() or (c<' ' or ord(c) > 0x7f) : c = data.next ()
            except StopIteration :
            finfo.Type = 'TEXT'

            Or possibly...

            try :
            ifilter (lambda c : c in string.printabl e, data).next ()
            except StopIteration :
            finfo.Type = 'TEXT'

            But these approaches both have a major failing - they don't express
            the intention well.

            Actually, there is a very clean and expressive approach that I'd use
            if I had access to my own library stuff...

            if mylib.exists (lambda c : c in string.printabl e, data) :
            finfo.Type = 'TEXT'

            with, in 'mylib'...

            def exists (pred, seq) :
            for i in seq :
            if pred(i) : return True
            return False

            ....which is, of course, cheating as that return is just as
            unstructured as a break - but I don't mind cheating too much when it
            is localised in a trivial library function.


            --
            Steve Horne

            steve at ninereeds dot fsnet dot co dot uk

            Comment

            • David Eppstein

              #21
              Re: &quot;for&qu ot; with &quot;else&quot ;?

              In article <ruumnv0dul3eb4 rrfj9vrn1qluhc9 64vls@4ax.com>,
              Stephen Horne <$$$$$$$$$$$$$$ $$$@$$$$$$$$$$$ $$$$$$$$$.co.uk > wrote:
              [color=blue]
              > I still think that exception-based approaches are far from painful for
              > this kind of stuff. However, with your example from binhex.py...
              >
              > for c in data:
              > if not c.isspace() and (c<' ' or ord(c) > 0x7f):
              > break
              > else:
              > finfo.Type = 'TEXT'[/color]
              ....[color=blue]
              > Actually, there is a very clean and expressive approach that I'd use
              > if I had access to my own library stuff...
              >
              > if mylib.exists (lambda c : c in string.printabl e, data) :
              > finfo.Type = 'TEXT'
              >
              > with, in 'mylib'...
              >
              > def exists (pred, seq) :
              > for i in seq :
              > if pred(i) : return True
              > return False
              >
              > ...which is, of course, cheating as that return is just as
              > unstructured as a break - but I don't mind cheating too much when it
              > is localised in a trivial library function.[/color]

              Haven't you inverted the logic here?
              You want a universal quantifier, not an existential one.

              --
              David Eppstein http://www.ics.uci.edu/~eppstein/
              Univ. of California, Irvine, School of Information & Computer Science

              Comment

              • Stephen Horne

                #22
                Re: &quot;for&qu ot; with &quot;else&quot ;?

                On Wed, 01 Oct 2003 20:10:03 -0700, David Eppstein
                <eppstein@ics.u ci.edu> wrote:
                [color=blue]
                >Haven't you inverted the logic here?
                >You want a universal quantifier, not an existential one.[/color]

                Yes, absolutely correct.

                I did some hacking around with different ways of representing this.
                The condition got inverted a few times. The final version was derived
                from something like this...

                if not mylib.exists (lambda c : c not in string.printabl e, data) :
                finfo.Type = 'TEXT'

                And I forgot to switch the quantifier when I inverted the logic for
                some bizarre reason. I guess that's what happens when you spend too
                much time obsessing over a piece of code - the longer you spend
                fussing over it the greater the odds of making a stupid mistake, and
                the less likely you are to spot it :-(

                So yes, the debugged version is...

                if mylib.always (lambda c : c in string.printabl e, data) :
                finfo.Type = 'TEXT'

                with...

                def always (pred, seq) :
                for i in seq :
                if not pred(i) : return False
                return True


                While these 'every' and 'always' functions are trivial, they are also
                pretty useful and obviously based on the common discrete maths
                notation so not unique to me.

                Could some simple functions like this have a place in the library
                somewhere?


                --
                Steve Horne

                steve at ninereeds dot fsnet dot co dot uk

                Comment

                • Alex Martelli

                  #23
                  Re: &quot;for&qu ot; with &quot;else&quot ;?

                  Stephen Horne wrote:
                  ...[color=blue]
                  > def always (pred, seq) :
                  > for i in seq :
                  > if not pred(i) : return False
                  > return True
                  >
                  > While these 'every' and 'always' functions are trivial, they are also
                  > pretty useful and obviously based on the common discrete maths
                  > notation so not unique to me.
                  >
                  > Could some simple functions like this have a place in the library
                  > somewhere?[/color]

                  They might, *EXCEPT* that "passing a callable" is out of fashion, and
                  out of favour, in the BDFL's current opinion -- and for good reasons,
                  too, although they MIGHT change in the future. The BDFL is on record
                  as regretting ever having accepted lambda into the language, and map,
                  filter and reduce into the languages' builtins; 3.0 (maybe in 2-3 years)
                  will be mostly about *simplification *, i.e. *removal* of some of the
                  "more than one way" things that have accreted over the years, and in
                  that release lambda and the 'functional'-ish built-ins are quite likely
                  to go (the latter might get moved to some backwards-compat module); such
                  new language features as list comprehensions have among their major pluses
                  the ability to NOT "pass callables" but rather write code in-line.

                  So I strongly doubt that any new construct *ENCOURAGING* people to
                  write "if always(lambda x: x>23, myseq):" and the like stands any
                  substantial chance of getting into the language and built-ins. I
                  could be wrong, of course; channeling Guido is Tim Peters' job, not
                  mine. But if you could figure out a GOOD Pythonic way to pass an
                  anonymous codeblock into a function, as e.g. Ruby and Smalltalk do
                  so naturally -- particularly a good SYNTAX, that's the hardest part --
                  THEN you might stand a good chance of "revolutionizin g" Python, at
                  least at 3.0 time. The ability to code predicates inline would be
                  key, here. Neither:
                  if always(lambda x: x>23, myseq):
                  ...
                  nor:
                  def gt23(x): return x>23
                  if always(gt23, myseq):
                  ...
                  are currently considered GOOD. What might be? I dunno. Ruby's
                  concepts (that the block, if present, always goes AFTER the function;
                  only one block can be there, at most; ...) might be acceptable limits
                  if they helped a good syntax; lambda's current limit (no statements,
                  just an expression -- and all the distortions THAT encourages) is
                  not acceptable.

                  E.g., suppose (and I doubt it IS so in reality:-) that making 'def'
                  do double duty was deemed acceptable; 'def' would denote a statement
                  when used like today, but it might also be an _expression_ instead,
                  if and only if used as the last actual-argument to a function, a la:

                  if always(myseq, def isgood(x): return x>23):
                  ...

                  or:

                  if always(myseq, def memoized_predic ate(x):
                  try: return _memo[x]
                  except KeyError:
                  _memo[x] = lots_of_computa tions(x)
                  return _memo[x]
                  ):
                  ...

                  THEN, _IF_ the BDFL liked this specific syntax (and, to repeat,
                  I strongly doubt he would!!!), you MIGHT well get your 'always',
                  'exists', etc etc -- and many other neat ideas currently held
                  in limbo because of the "passing a callable" issue might also
                  get "unblocked" . Among the problems is, there is no precedent
                  for an _expression_ being whitespace/indentation sensitive, yet
                  something like that would be indispensable for any such idea...


                  Alex

                  Comment

                  • Stephen Horne

                    #24
                    Re: &quot;for&qu ot; with &quot;else&quot ;?

                    On Thu, 02 Oct 2003 09:26:43 GMT, Alex Martelli <aleax@aleax.it >
                    wrote:
                    [color=blue]
                    >They might, *EXCEPT* that "passing a callable" is out of fashion, and
                    >out of favour, in the BDFL's current opinion -- and for good reasons,
                    >too, although they MIGHT change in the future.[/color]

                    I agree mostly. At present, first-class functions are very powerful
                    tools, but they certainly have some drawbacks.

                    The trouble, as you said, is the syntax. At present, the most natural
                    syntax happens in list comprehensions - where the language grammar
                    isolates the 'code block'. It's hard to think of a good syntax which
                    (1) doesn't recreate lambda to explicitly mark out the 'code block',
                    and (2) can exist within Pythons existing dynamic model.

                    :-(


                    --
                    Steve Horne

                    steve at ninereeds dot fsnet dot co dot uk

                    Comment

                    Working...