Питонические способы использования "else" в цикле for

Я почти никогда не замечал программу python, которая использует else в цикле for.

Недавно я использовал его для выполнения действия на основе условия переменной цикла при выходе; как он находится в области.

Что такое питонический способ использования else в цикле for? Существуют ли какие-либо заметные варианты использования?

И, да. Мне не нравится использовать оператор break. Я предпочел бы установить условие цикла. Смогу ли я получить какую-либо выгоду из этого, если мне не нравится использовать оператор break в любом случае.

Стоит отметить, что для цикла имеет значение else с момента создания языка, первая версия.

Ответ 1

Что может быть больше pythonic, чем PyPy?

Посмотрите, что я обнаружил, начиная с строки 284 в ctypes_configure/configure.py:

    for i in range(0, info['size'] - csize + 1, info['align']):
        if layout[i:i+csize] == [None] * csize:
            layout_addfield(layout, i, ctype, '_alignment')
            break
    else:
        raise AssertionError("unenforceable alignment %d" % (
            info['align'],))

И здесь, из строки 425 в pypy/annotation/annrpython.py(clicky)

if cell.is_constant():
    return Constant(cell.const)
else:
    for v in known_variables:
        if self.bindings[v] is cell:
            return v
    else:
        raise CannotSimplify

В pypy/annotation/binaryop.py, начиная с строки 751:

def is_((pbc1, pbc2)):
    thistype = pairtype(SomePBC, SomePBC)
    s = super(thistype, pair(pbc1, pbc2)).is_()
    if not s.is_constant():
        if not pbc1.can_be_None or not pbc2.can_be_None:
            for desc in pbc1.descriptions:
                if desc in pbc2.descriptions:
                    break
            else:
                s.const = False    # no common desc in the two sets
    return s

Не-однострочный в pypy/annotation/classdef.py, начиная с строки 176:

def add_source_for_attribute(self, attr, source):
    """Adds information about a constant source for an attribute.
    """
    for cdef in self.getmro():
        if attr in cdef.attrs:
            # the Attribute() exists already for this class (or a parent)
            attrdef = cdef.attrs[attr]
            s_prev_value = attrdef.s_value
            attrdef.add_constant_source(self, source)
            # we should reflow from all the reader position,
            # but as an optimization we try to see if the attribute
            # has really been generalized
            if attrdef.s_value != s_prev_value:
                attrdef.mutated(cdef) # reflow from all read positions
            return
    else:
        # remember the source in self.attr_sources
        sources = self.attr_sources.setdefault(attr, [])
        sources.append(source)
        # register the source in any Attribute found in subclasses,
        # to restore invariant (III)
        # NB. add_constant_source() may discover new subdefs but the
        #     right thing will happen to them because self.attr_sources
        #     was already updated
        if not source.instance_level:
            for subdef in self.getallsubdefs():
                if attr in subdef.attrs:
                    attrdef = subdef.attrs[attr]
                    s_prev_value = attrdef.s_value
                    attrdef.add_constant_source(self, source)
                    if attrdef.s_value != s_prev_value:
                        attrdef.mutated(subdef) # reflow from all read positions

Позже в том же файле, начиная с строки 307, пример с подсветкой комментария:

def generalize_attr(self, attr, s_value=None):
    # if the attribute exists in a superclass, generalize there,
    # as imposed by invariant (I)
    for clsdef in self.getmro():
        if attr in clsdef.attrs:
            clsdef._generalize_attr(attr, s_value)
            break
    else:
        self._generalize_attr(attr, s_value)

Ответ 2

Если у вас есть цикл for, у вас действительно нет оператора условий. Поэтому перерыв - это ваш выбор, если вы хотите прервать, а затем еще можете отлично справиться с ситуацией, когда вы не были счастливы.

for fruit in basket:
   if fruit.kind in ['Orange', 'Apple']:
       fruit.eat()
       break
else:
   print 'The basket contains no desirable fruit'

Ответ 3

В принципе, он упрощает любой цикл, который использует логический флаг следующим образом:

found = False                # <-- initialize boolean
for divisor in range(2, n):
    if n % divisor == 0:
        found = True         # <-- update boolean
        break  # optional, but continuing would be a waste of time

if found:                    # <-- check boolean
    print n, "is composite"
else:
    print n, "is prime"

и позволяет пропустить управление флагом:

for divisor in range(2, n):
    if n % divisor == 0:
        print n, "is composite"
        break
else:
    print n, "is prime"

Обратите внимание, что уже существует естественное место для выполнения кода, когда вы находите делитель - прямо перед break. Единственная новая функция здесь - место для кода, которое нужно выполнить, когда вы пробовали все делители и не нашли.

Это помогает только в сочетании с break. Вам все еще нужны логические операции, если вы не можете сломаться (например, потому что вы ищете последнее совпадение или должны отслеживать несколько условий параллельно).

О, и BTW, это работает так же, как и петли.

любые/все

В настоящее время, если единственная цель цикла - ответ "да" или "нет", вы можете записать его намного короче с помощью функций any()/all() с выражением генератора или генератора, которое дает логические значения:

if any(n % divisor == 0 
       for divisor in range(2, n)):
    print n, "is composite"
else:
    print n, "is prime"

Обратите внимание на элегантность! Код 1:1, что вы хотите сказать!

[Это так же эффективно, как и цикл с break, потому что функция any() закорочена, работает только выражение генератора до тех пор, пока оно не будет True. На самом деле это обычно даже быстрее, чем цикл. Упрощенный код Python имеет тенденцию меньше слышать.]

Это менее эффективно, если у вас есть другие побочные эффекты - например, если вы хотите найти делитель. Вы все еще можете это сделать (ab), используя тот факт, что значение не-0 истинно в Python:

divisor = any(d for d in range(2, n) if n % d == 0)
if divisor:
    print n, "is divisible by", divisor
else:
    print n, "is prime"

но, как вы видите, это становится неустойчивым - не работает, если 0 - возможное значение делителя...

Ответ 4

Без использования break блоки else не имеют преимуществ для операторов for и while. Следующие два примера эквивалентны:

for x in range(10):
  pass
else:
  print "else"

for x in range(10):
  pass
print "else"

Единственная причина использования else с for или while заключается в том, чтобы что-то делать после цикла, если оно заканчивается нормально, т.е. без явного break.

После долгих размышлений я могу, наконец, придумать случай, когда это может быть полезно:

def commit_changes(directory):
    for file in directory:
        if file_is_modified(file):
            break
    else:
        # No changes
        return False

    # Something has been changed
    send_directory_to_server()
    return True

Ответ 5

Возможно, лучший ответ исходит из официального руководства Python:

разбить и продолжить сообщения, а также Клаузы на циклах:

Операторы цикла могут иметь другое оговорка; он выполняется, когда цикл заканчивается исчерпанием list (with for) или когда условие становится ложным (пока), но не когда цикл завершается перерывом Заявление

Ответ 6

Я познакомился с замечательной идиомой, в которой вы можете использовать схему for/break/else с итератором, чтобы сохранить как время, так и LOC. Приведенный пример искал кандидата для неполного пути. Если вы хотите увидеть исходный контекст, см. исходный вопрос.

def match(path, actual):
    path = path.strip('/').split('/')
    actual = iter(actual.strip('/').split('/'))
    for pathitem in path:
        for item in actual:
            if pathitem == item:
                break
        else:
            return False
    return True

Что делает использование for/else настолько отличным, что это элегантность, позволяющая избежать жонглирования запутанным булевым. Без else, но надеясь достичь такого же количества короткого замыкания, это может быть написано так:

def match(path, actual):
    path = path.strip('/').split('/')
    actual = iter(actual.strip('/').split('/'))
    failed = True
    for pathitem in path:
        failed = True
        for item in actual:
            if pathitem == item:
                failed = False
                break
        if failed:
            break
    return not failed

Я думаю, что использование else делает его более элегантным и более очевидным.

Ответ 7

Здесь вы идете:

a = ('y','a','y')
for x in a:
  print x,
else:
  print '!'

Это для caboose.

изменить:

# What happens if we add the ! to a list?

def side_effect(your_list):
  your_list.extend('!')
  for x in your_list:
    print x,

claimant = ['A',' ','g','u','r','u']
side_effect(claimant)
print claimant[-1]

# oh no, claimant now ends with a '!'

изменить:

a = (("this","is"),("a","contrived","example"),("of","the","caboose","idiom"))
for b in a:
  for c in b:
    print c,
    if "is" == c:
      break
  else:
    print