Пітонічний спосіб поєднання циклу FOR та IF заяви


266

Я знаю, як використовувати як для циклів, так і якщо оператори в окремих рядках, наприклад:

>>> 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 і деяких, якщо тверджень. Щось, на що я очікував, виглядає так:

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

Це просто не так, як повинен працювати пітон?


23
Ось так і є ... не надто ускладнюйте речі, намагаючись спростити їх. Pythonic не означає уникати кожного явного forциклу та ifтвердження.
Фелікс Клінг

2
Ви можете використовувати список, згенерований для розуміння списку, у циклі for. Це дещо виглядатиме як ваш останній приклад.
Яків

Отже, переходячи до обробки, який найшвидший спосіб поєднати цикл for з оператором if, якщо оператор if виключає значення, які вже були зіставлені, і список постійно зростає під час ітерації циклу?
ChewyChunks

3
@Chewy, правильні структури даних зроблять код швидшим, а не синтаксичним цукром. Наприклад, 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
Я дуже сумую за тим, щоб Python міг сказати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

Відповідно до «Дзен Пітона» (якщо вам цікаво, чи ваш код «пітонічний», це саме місце):

  • Красиве краще, ніж потворне.
  • Явне краще, ніж неявне.
  • Простий - краще, ніж складний.
  • Квартира краще, ніж вкладена.
  • Читання рахується.

Пітонічний спосіб отримання двох 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

Звучить, як із коментарів нижче, я повинен вивчати генератори. Я ніколи їх не використовував. Дякую. Чи генератор швидший, ніж еквівалентна комбінація тверджень FOR та IF? Я також використовував набори, але іноді зайві елементи в списку - це інформація, яку я не можу відкинути.
ChewyChunks

@ChewyChunks: Генератори - не єдиний спосіб бути пітоніком!
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)))

4
filter(a.__contains__, xyz). Зазвичай, коли люди вживають лямбда, їм справді потрібно щось набагато простіше.
Векі

Я думаю, ти щось неправильно зрозумів. __contains__це метод, як і будь-який інший, тільки він є спеціальним методом, тобто він може бути покликаний опосередковано оператором ( inв даному випадку). Але це також можна назвати безпосередньо, це частина публічного API. Приватні імена конкретно визначаються як такі, що мають, щонайменше, один кінцевий підкреслення, щоб забезпечити виняток для імен спеціальних методів - і вони підлягають маніпулюванню іменами, коли вони лексично знаходяться в областях класу. Див. Docs.python.org/3/reference/datamodel.html#specialnames та docs.python.org/3.6/tutorial/classes.html#private-variables .
Векі

Це, звичайно, добре, але два імпорту просто, щоб мати можливість посилатися на метод, доступний за допомогою лише атрибуту, здається дивним (оператори, як правило, використовуються, коли подвійна відправка є важливою, але inодноразово відправляється wrt правою операндом). Крім того, зауважте, що operatorтакож експорт containsметоду під назвою __contains__, так що, безумовно, це не приватне ім'я. Я думаю, що вам просто доведеться навчитися жити з тим фактом, що не кожен подвійний підкреслення означає «триматися подалі». : -]
Veky

Я думаю, що ваші lambdaпотреби виправлення включатимуть not: lambda w: not w in a, xyz
javadba

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

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помітьте паролі в print;))


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])

Дуже дзен, @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: ....
Lauritz V. Thaulow

@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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.