Перша ітерація циклу “For”


77

Я хотів би запитати, чи існує елегантний пітонічний спосіб виконання якоїсь функції на першій ітерації циклу. Єдина можливість, яку я можу придумати, це:

first = True
for member in something.get():
    if first:
        root.copy(member)
        first = False
    else:
        somewhereElse.copy(member)
    foo(member)

Відповіді:


46

У вас є кілька варіантів дизайну шаблону Head-Tail .

seq= something.get()
root.copy( seq[0] )
foo( seq[0] )
for member in seq[1:]:
    somewhereElse.copy(member)
    foo( member )

Або це

seq_iter= iter( something.get() )
head = seq_iter.next()
root.copy( head )
foo( head )
for member in seq_iter:
    somewhereElse.copy( member )
    foo( member )

Люди скигли, що це якось не "СУХО", оскільки код "зайвого foo (member)". Це смішне твердження. Якщо це було правдою, тоді всі функції можна було використовувати лише один раз. Який сенс визначати функцію, якщо ви можете мати лише одне посилання?


Ви забруднюєте простір імен додатковим memberспособом цим способом.
Skilldrick

5
Технічно це не СУХО, оскільки ти повторюєш семантику того, як ти поводишся з будь-яким учасником у двох місцях, але оскільки код такий короткий і тісний, я думаю, що ця справа спірна. Однак, якби між ними було більше коду або якщо вони були абстраговані в окремі функції, то я справедливо зазначу, що це порушує принцип СУХОСТІ.
Даніель Брюс,

@Skilldrick: Приклад коду також забруднюється простір імен з двома різними значеннями для одного імені, member. Один memberбув головою; інший membersде хвіст. Проте всі вони були членами. Я не впевнений, про що ви говорите.
S.Lott

@ Даніель Брюс: Я не бачу, як використання fooв двох (або більше) місцях порушило СУХУ. Ця функція використовується повторно. Хіба це не суть визначення функції?
S.Lott

СУХИЙ чи ні, це найпростіше прочитати, якщо ви не повторюєте більше одного або двох рядків коду, і це перевершує інші проблеми моєї книги. Якщо послідовність довга, і ви не хочете створювати тимчасову копію all-elements-but-one, ви можете використовувати itertools.islice.
musicinmybrain

74

Щось подібне мало би спрацювати.

for i, member in enumerate(something.get()):
    if i == 0:
         # Do thing
    # Code for everything

Однак я настійно рекомендую подумати над своїм кодом, щоб перевірити, чи дійсно вам доведеться робити це таким чином, оскільки він начебто "брудний". Краще було б отримати елемент, який потребує спеціальної обробки спереду, а потім регулярно обробляти всі інші цикли.

Єдина причина, за якою я міг бачити, що не роблю цього таким чином, полягає у великому списку, який ви отримували б із виразу генератора (який ви не хотіли б отримувати заздалегідь, оскільки він не міститься в пам'яті), або подібних ситуацій .


Насправді, вам не потрібно мати великий список. Якщо something.get () повертає генератор (на відміну від списку), тоді ви золоті.
Пітер Роуелл

Як би це працювало для циклу, який має кортеж? тобто for i, a, b, c, in os.walk(input_dir):? Це дає ValueError: need more than 3 values to unpack.
p014k

У вашому прикладі коду є кілька помилок: кінцева кома у виразі for і відсутність виклику enumerate (). Вам довелося б розпакувати кортеж всередині цикла for: python for i,tuple in enumerate(os.walk(...)): a, b, c = tuple
Даніель Брюс

Але це перевірить стан стільки разів, скільки елементів у контейнері щось ...
aderchox

13

як щодо:

my_array = something.get()
for member in my_array:
    if my_array.index(member) == 0:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

або можливо:

for index, member in enumerate(something.get()):
    if index == 0:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

Документація індекс-методу .


1
Перший варіант не працює, якщо my_array має інші члени, які порівнюються з першим членом.
Joooeey

Це безпечніше і легше читається, якщо ви пишете if member is my_array[0]. Але це все одно не працює, якщо my_array посилається на той самий об'єкт з індексом 0 та іншим індексом.
Joooeey,

6

Це працює:

for number, member in enumerate(something.get()):
    if not number:
        root.copy(member)
    else:
        somewhereElse.copy(member)
    foo(member)

Однак у більшості випадків я б запропонував просто повторити whatever[1:]і виконати кореневі дії поза циклом; це, як правило, читабельніше. Звичайно, це залежить від вашого випадку використання.


1
-1: "не число"? Це справді велелюдно незрозуміло. Що не так із числом == 0?
S.Lott

1
Я б використав число == 0, оскільки воно відповідає семантиці, яка вас цікавить (перший елемент ітерабельного файлу, який має індекс 0). Однак цей синтаксис далекий від “надзвичайно неясного”, хоча більш корисний для перевірки порожнечі послідовності.
musicinmybrain

1
Тоді зробіть це число == 0, якщо це здається зрозумілішим. 0 - єдине число, яке обчислюється як False.
balpha

6

Тут я міг би прийти з пітонічною ідіомою, яка може виглядати "недоречно". Хоча, швидше за все, я б використав форму, яку ви запропонували, задаючи питання, просто для того, щоб код залишався більш очевидним, хоча і менш елегантним.

def copy_iter():
    yield root.copy
    while True:
        yield somewhereElse.copy

for member, copy in zip(something.get(), copy_iter()):
    copy(member)
    foo(member)

(вибачте - перший, який я опублікував, перед редагуванням, форма не працювала, я забув насправді отримати ітератор для об'єкта 'copy')


він він, я прийшов до такого ж рішення , перш ніж бачити ваше, але використовуючи itertools :-)
FORtran

6

Я думаю, це досить елегантно, але, можливо, занадто заплутано для того, що робить ...

from itertools import chain, repeat, izip
for place, member in izip(chain([root], repeat(somewhereElse)), something.get()):
    place.copy(member)
    foo(member)

3

Якщо something.get () повторює щось, ви можете зробити це також наступним чином:

root.copy(something.get())

for member in something.get():
  #  the rest of the loop

3

Я думаю, що перше рішення S.Lott є найкращим, але є інший вибір, якщо ви використовуєте досить недавній python (> = 2,6, я думаю, оскільки izip_longest здається недоступним до цієї версії), який дозволяє робити різні речі для перший елемент і наступний, і його можна легко змінити, щоб робити окремі операції для 1-го, 2-го, 3-го елемента ... також.

from itertools import izip_longest

seq = [1, 2, 3, 4, 5]

def headfunc(value):
    # do something
    print "1st value: %s" % value

def tailfunc(value):
    # do something else
    print "this is another value: %s" % value

def foo(value):
    print "perform this at ANY iteration."

for member, func in izip_longest(seq, [headfunc], fillvalue=tailfunc):
    func(member)
    foo(member)

Це, можливо, розумно, але це далеко не ясно чи інтуїтивно.
Аарон Макміллін,

3

Як щодо використання iterта споживання першого елемента?

Редагувати: Повертаючись до питання ОП, є загальна операція, яку ви хочете виконати з усіма елементами, а потім одна операція, яку ви хочете виконати з першим елементом, а інша - з рештою.

Якщо це лише один виклик функції, я б сказав, просто напишіть його двічі. Це не закінчить світ. Якщо це більше залучено, ви можете скористатися декоратором, щоб обгортати свою функцію "перша" та "відпочинок" загальною операцією.

def common(item):
    print "common (x**2):", item**2

def wrap_common(func):
    """Wraps `func` with a common operation"""
    def wrapped(item):
        func(item)
        common(item)
    return wrapped

@wrap_common
def first(item):
    """Performed on first item"""
    print "first:", item+2

@wrap_common
def rest(item):
    """Performed on rest of items"""
    print "rest:", item+5

items = iter(range(5))
first(items.next())

for item in items:
    rest(item)

Вихід:

first: 2
common (x**2): 0
rest: 6
common (x**2): 1
rest: 7
common (x**2): 4
rest: 8
common (x**2): 9
rest: 9
common (x**2): 16

або ви можете зробити фрагмент:

first(items[0])
for item in items[1:]:
    rest(item)

1

Ви не можете зробити це root.copy(something.get())раніше циклу?

РЕДАГУВАТИ: Вибачте, я пропустив другий біт. Але ви зрозуміли загальну ідею. В іншому випадку перелічити і перевірити 0?

EDIT2: Добре, позбувся дурної другої ідеї.


1

Я не знаю Python, але я використовую майже точну схему вашого прикладу.
Що я також роблю if, так це найчастіший стан, тому зазвичай перевіряю, if( first == false )
чому? для довгих циклів first один раз буде істинним, а весь інший раз буде помилковим, що означає, що у всіх циклах, крім першого, програма перевіряє стан і переходить до іншої частини.
Перевіривши, що спочатку це помилка, буде лише один перехід до іншої частини. Я насправді не знаю, чи це взагалі додає ефективності, але я все одно роблю це, просто щоб бути у спокої зі своїм внутрішнім ботаніком.

PS: Так, я знаю, що, вводячи частину if, вона також повинна перестрибнути іншу частину, щоб продовжити виконання, тому, ймовірно, мій спосіб робити це марно, але це приємно. : D


0

Ваше запитання суперечливе. Ви говорите "робіть щось лише на першій ітерації", а насправді ви говорите зробити щось інше на першій / наступній ітераціях. Ось як би я спробував це зробити:

copyfn = root.copy
for member in something.get():
    copyfn(member)
    foo(member)
    copyfn = somewhereElse.copy

0

Ось, що мені підходить

    dup_count = 0
    for x in reversed(dup_list):
        dup_count += 1
        if dup_count == 1:
            print("First obj {}: {}".format(dup_count,x))
        else:
            print("Object # {}:  {}".format( dup_count,x  ))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.