Pythonic способ объединить цикл FOR и оператор IF

266

Я знаю, как использовать как для циклов, так и операторов if в отдельных строках, таких как:

>>> a = [2,3,4,5,6,7,8,9,0]
... xyz = [0,12,4,6,242,7,9]
... for x in xyz:
...     if x in a:
...         print(x)
0,4,6,7,9

И я знаю, что могу использовать понимание списка, чтобы объединить их, когда утверждения просты, такие как:

print([x for x in xyz if x in a])

Но чего я не могу найти, так это хорошего примера (для копирования и изучения), демонстрирующего сложный набор команд (не просто «print x»), которые выполняются после комбинации цикла for и некоторых операторов if. Что-то, что я ожидаю, выглядит так:

for x in xyz if x not in a:
    print(x...)

Разве это не то, как Python должен работать?

ChewyChunks
источник
23
Вот как это ... не переусердствуйте, пытаясь упростить их. Pythonic не означает избегать каждого явного forцикла и ifутверждения.
Феликс Клинг
2
Вы можете использовать список, созданный в вашем понимании списка, в цикле for. Это будет выглядеть как ваш последний пример.
Джейкоб
Итак, приступая к обработке, какой самый быстрый способ объединить цикл for с оператором if, если оператор if исключает значения, которые уже были сопоставлены, и список постоянно увеличивается во время итерации цикла for?
ChewyChunks
3
@ Чеви, правильные структуры данных сделают код быстрее, а не синтаксический сахар. Например, x in aмедленно, если aэто список.
Ник Дандулакис
1
Это Python, интерпретируемый язык; почему кто-то обсуждает, насколько быстрый код вообще?
ArtOfWarfare

Ответы:

323

Вы можете использовать выражения генератора следующим образом:

gen = (x for x in xyz if x not in a)

for x in gen:
    print x
Кугель
источник
1
gen = (y for (x,y) in enumerate(xyz) if x not in a)возвращает >>> 12когда я печатаю for x in gen: print x- так почему неожиданное поведение с перечислением?
ChewyChunks
9
Возможно, но не лучше, чем оригинал для и если блоков.
Майк Грэм,
1
@ChewyChunks. Это будет работать, но вызов перечисления является излишним.
Johnsyweb
132
Я очень скучаю по питону, что могу сказатьfor x in xyz if x:
bgusach
10
for x in (x for x in xyz if x not in a):работает для меня, но почему вы не можете просто быть в состоянии сделать for x in xyz if x not in a:, я не уверен ...
Мэтт Уэнам
34

В соответствии с Zen of Python (если вам интересно, является ли ваш код "Pythonic", это то, что нужно):

  • Красиво лучше, чем безобразно.
  • Явное лучше, чем неявное.
  • Простое лучше, чем сложное.
  • Квартира лучше, чем вложенная.
  • Читаемость имеет значение.

Pythonic способ получения двух s:sorted intersectionset

>>> sorted(set(a).intersection(xyz))
[0, 4, 6, 7, 9]

Или те элементы, которые есть, xyzно не в a:

>>> sorted(set(xyz).difference(a))
[12, 242]

Но для более сложного цикла вы можете захотеть сгладить его, выполнив итерацию по имени генератора с хорошим именем и / или вызвав функцию с именем с хорошим именем. Попытка уместить все в одну строку редко бывает «питонической».


Обновите следующие дополнительные комментарии на ваш вопрос и принятый ответ

Я не уверен, что вы пытаетесь сделать enumerate, но если aэто словарь, вы, вероятно, хотите использовать ключи, например так:

>>> a = {
...     2: 'Turtle Doves',
...     3: 'French Hens',
...     4: 'Colly Birds',
...     5: 'Gold Rings',
...     6: 'Geese-a-Laying',
...     7: 'Swans-a-Swimming',
...     8: 'Maids-a-Milking',
...     9: 'Ladies Dancing',
...     0: 'Camel Books',
... }
>>>
>>> xyz = [0, 12, 4, 6, 242, 7, 9]
>>>
>>> known_things = sorted(set(a.iterkeys()).intersection(xyz))
>>> unknown_things = sorted(set(xyz).difference(a.iterkeys()))
>>>
>>> for thing in known_things:
...     print 'I know about', a[thing]
...
I know about Camel Books
I know about Colly Birds
I know about Geese-a-Laying
I know about Swans-a-Swimming
I know about Ladies Dancing
>>> print '...but...'
...but...
>>>
>>> for thing in unknown_things:
...     print "I don't know what happened on the {0}th day of Christmas".format(thing)
...
I don't know what happened on the 12th day of Christmas
I don't know what happened on the 242th day of Christmas
Johnsyweb
источник
Похоже, из комментариев ниже, я должен изучать генераторы. Я никогда не использовал их. Спасибо. Является ли генератор быстрее, чем эквивалентная комбинация операторов FOR и IF? Я также использовал наборы, но иногда избыточные элементы в списке - это информация, которую я не могу отбросить.
ChewyChunks
@ChewyChunks: Генераторы не единственный способ быть Pythonic!
Johnsyweb
3
@Johnsyweb, если вы собираетесь процитировать дзен Python: «Должен быть один - и желательно только один - очевидный способ сделать это».
Wooble
@ Wooble: Там должно быть. Я процитировал этот раздел в своем ответе на другой вопрос примерно в то же время!
Johnsyweb
18

Я лично думаю, что это самая красивая версия:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]
for x in filter(lambda w: w in a, xyz):
  print x

редактировать

если вы очень заинтересованы в том, чтобы избегать использования лямбды, вы можете использовать частичное применение функции и использовать модуль оператора (который обеспечивает функции большинства операторов).

https://docs.python.org/2/library/operator.html#module-operator

from operator import contains
from functools import partial
print(list(filter(partial(contains, a), xyz)))
Alex
источник
4
filter(a.__contains__, xyz), Обычно, когда люди используют лямбду, им действительно нужно нечто гораздо более простое.
Veky
Я думаю, вы что-то неправильно поняли. __contains__это метод, как и любой другой, только это специальный метод, то есть он может быть вызван косвенно оператором ( inв данном случае). Но он также может быть вызван напрямую, он является частью общедоступного API. Частные имена специально определены как имеющие не более одного завершающего подчеркивания, чтобы обеспечить исключение для специальных имен методов - и они подвергаются искажению имени, когда лексически в пределах классов. См. Docs.python.org/3/reference/datamodel.html#specialnames и docs.python.org/3.6/tutorial/classes.html#private-variables .
Veky
Это, конечно, нормально, но два импорта, просто чтобы иметь возможность ссылаться на метод, который доступен с использованием только атрибута, кажутся странными (операторы обычно используются, когда двойная диспетчеризация необходима, но inона отправляется отдельно с правым операндом). Кроме того, обратите внимание, что operatorтакже экспортирует containsметод под именем __contains__, так что это, конечно, не личное имя. Я думаю, вам просто нужно научиться жить с тем фактом, что не каждое двойное подчеркивание означает «держаться подальше». : -]
Veky
Я думаю, что ваши lambdaпотребности исправить, чтобы включить not: lambda w: not w in a, xyz
Джавадба
Фильтр кажется более элегантным, особенно для сложных условий , которые стали бы определенными функциями вместо лямбды, может назвать функцию лямбды бы добавить читаемость, генератор кажется лучше , когда итерированные элементы некоторые изменения в элементах списка
Khanis Rok
16

Ниже приведено упрощение / один вкладыш из принятого ответа:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]

for x in (x for x in xyz if x not in a):
    print(x)

12
242

Обратите внимание, что он generatorбыл встроен . Это было проверено на python2.7и python3.6 (обратите внимание на Parens в print;))

javadba
источник
10

Я бы, вероятно, использовал:

for x in xyz: 
    if x not in a:
        print x...
Вим Фейен
источник
@KirillTitov Да, python - это принципиально нефункциональный язык (это чисто императивное кодирование - и я согласен с автором этого ответа в том, что именно так Python настроен для написания. Попытка использовать функционалы приводит к плохому чтению или неработоспособности. pythonicЯ могу кодировать функционально на любом другом языке, который я использую (scala, kotlin, javascript, R, swift, ..), но трудно / неловко в python
javadba
9
a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]  
set(a) & set(xyz)  
set([0, 9, 4, 6, 7])
Kracekumar
источник
Very Zen, @lazyr, но это не помогло бы мне улучшить сложный блок кода, который зависит от перебора одного списка и игнорирования соответствующих элементов в другом списке. Быстрее ли обрабатывать первый список как набор и сравнивать объединение / различие со вторым растущим списком «игнорируемых»?
ChewyChunks
Попробуйте этоimport time a = [2,3,4,5,6,7,8,9,0] xyz = [0,12,4,6,242,7,9] start = time.time() print (set(a) & set(xyz)) print time.time() - start
Kracekumar
@ChewyChunks, если какой-либо из списков изменится во время итерации, вероятно, будет быстрее проверить каждый элемент по списку игнорирования - за исключением того, что вы должны сделать его набором игнорирования. Проверка членства в наборах очень быстро: if x in ignore: ....
Лауриц В. Таулов
@lazyr Я просто переписал свой код, используя набор игнорирования над списком игнорирования. Похоже, обрабатывать время гораздо медленнее. (Честно говоря, я сравнивал использование, if set(a) - set(ignore) == set([]):так что, возможно, именно поэтому это было намного медленнее, чем проверка членства. Я проверю это снова в будущем на гораздо более простом примере, чем то, что я пишу.
ChewyChunks
5

Вы также можете использовать генераторы , если выражения генератора становятся слишком сложными или сложными:

def gen():
    for x in xyz:
        if x in a:
            yield x

for x in gen():
    print x
Лауриц В. Таулов
источник
Это немного более полезно для меня. Я никогда не смотрел на генераторы. Они звучат страшно (потому что я видел их в модулях, которые обычно было неудобно использовать).
ChewyChunks
2

Используйте intersectionилиintersection_update

  • пересечение :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    ans = sorted(set(a).intersection(set(xyz)))
  • intersection_update :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    b = set(a)
    b.intersection_update(xyz)

    тогда bтвой ответ

Чунг-Йен Хунг
источник
2

Мне понравился ответ Алекса , потому что фильтр оказывается в точности , если применяется к списку, так что если вы хотите , чтобы исследовать подмножество списка данного условия, это кажется самым естественным способом

mylist = [1,2,3,4,5]
another_list = [2,3,4]

wanted = lambda x:x in another_list

for x in filter(wanted, mylist):
    print(x)

этот метод полезен для разделения проблем, если функция условия изменяется, единственный код, который нужно возиться, это сама функция

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

wanted = lambda x:(x**0.5) > 10**0.3

for x in filter(wanted, mylist):
    print(x)

Метод генератора кажется лучше, когда вам не нужны члены списка, но есть модификация упомянутых членов, которая кажется более подходящей для генератора

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

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.5 for x in mylist if wanted(x))

for x in generator:
    print(x)

Также фильтры работают с генераторами, хотя в этом случае это неэффективно

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

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.9 for x in mylist)

for x in filter(wanted, generator):
    print(x)

Но, конечно, было бы неплохо написать так:

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

wanted = lambda x:(x**0.5) > 10**0.3

# for x in filter(wanted, mylist):
for x in mylist if wanted(x):
    print(x)
Ханис Рок
источник
0

Простой способ найти уникальные общие элементы списков a и b:

a = [1,2,3]
b = [3,6,2]
for both in set(a) & set(b):
    print(both)
peawormsworth
источник