Pythonic способы использовать 'else' в цикле for - PullRequest
13 голосов
/ 26 марта 2009

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

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

Какой питонный способ использовать else в цикле for? Есть ли заметные варианты использования?

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

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

Ответы [ 7 ]

15 голосов
/ 26 марта 2009

Что может быть более питоническим, чем 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's 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)
6 голосов
/ 26 марта 2009

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

for fruit in basket:
   if fruit.kind in ['Orange', 'Apple']:
       fruit.eat()
       break
else:
   print 'The basket contains no desirable fruit'
4 голосов
/ 30 декабря 2009

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

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. Вам все еще нужны логические значения, если вы не можете разорвать (например, потому что вы ищете последнее совпадение или вам нужно отслеживать несколько условий параллельно).

Да, и кстати, это работает и для циклов while.

/ всех

В настоящее время, если единственной целью цикла является ответ «да» или «нет», вы можете написать его намного короче с помощью функций 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), используя тот факт, что в Python значение, отличное от 0, истинно:

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 было возможным значением делителя ...

3 голосов
/ 26 марта 2009

Без использования блоков 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
0 голосов
/ 01 января 2014

Я познакомился с замечательной идиомой, в которой вы можете использовать схему 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 делает его более элегантным и более очевидным.

0 голосов
/ 26 марта 2009

Вот, пожалуйста:

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

Это для камбуза.

редактирование:

# 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...