Cela fait ce que vous voulez et fonctionnera dans presque tous les cas:
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True
L'expression 'a','b' in ['b', 'a', 'foo', 'bar']
ne fonctionne pas comme prévu car Python l'interprète comme un tuple:
>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)
Autres options
Il existe d'autres moyens d'exécuter ce test, mais ils ne fonctionneront pas pour autant de types d'entrées différents. Comme le souligne Kabie , vous pouvez résoudre ce problème en utilisant des ensembles ...
>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True
...quelquefois:
>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Les ensembles ne peuvent être créés qu'avec des éléments hachables. Mais l'expression du générateur all(x in container for x in items)
peut gérer presque tous les types de conteneurs. La seule exigence est qu'il container
soit réitérable (c'est-à-dire pas un générateur). items
peut être tout itérable du tout.
>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True
Tests de vitesse
Dans de nombreux cas, le test du sous-ensemble sera plus rapide que all
, mais la différence n'est pas choquante - sauf lorsque la question n'est pas pertinente car les ensembles ne sont pas une option. La conversion de listes en ensembles uniquement dans le but d'un test comme celui-ci n'en vaudra pas toujours la peine. Et la conversion de générateurs en ensembles peut parfois être un gaspillage incroyable, ralentissant les programmes de plusieurs ordres de grandeur.
Voici quelques repères à titre d'illustration. La plus grande différence vient quand les deux container
et items
sont relativement faibles. Dans ce cas, l'approche du sous-ensemble est d'environ un ordre de grandeur plus rapide:
>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Cela ressemble à une grande différence. Mais tant qu'il container
s'agit d'un ensemble, il all
est toujours parfaitement utilisable à des échelles beaucoup plus grandes:
>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
L'utilisation des tests de sous-ensembles est encore plus rapide, mais seulement d'environ 5x à cette échelle. L'augmentation de la vitesse est due à l' c
implémentation rapide de Python set
, mais l'algorithme fondamental est le même dans les deux cas.
Si vous items
êtes déjà stocké dans une liste pour d'autres raisons, vous devrez les convertir en un ensemble avant d'utiliser l'approche de test de sous-ensemble. Ensuite, l'accélération tombe à environ 2,5x:
>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Et si votre container
est une séquence, et doit d'abord être convertie, alors l'accélération est encore plus petite:
>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Le seul moment où nous obtenons des résultats désastreusement lents, c'est lorsque nous partons container
en séquence:
>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Et bien sûr, nous ne le ferons que si nous le devons. Si tous les éléments de bigseq
sont hachables, nous le ferons à la place:
>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
C'est juste 1,66 fois plus rapide que l'alternative ( set(bigseq) >= set(bigsubseq)
, chronométré ci-dessus à 4,36).
Les tests de sous-ensembles sont donc généralement plus rapides, mais pas avec une marge incroyable. D'un autre côté, regardons quand all
est plus rapide. Que faire si items
est long de dix millions de valeurs et est susceptible d'avoir des valeurs qui ne sont pas incluses container
?
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
La conversion du générateur en un ensemble s'avère être un gaspillage incroyable dans ce cas. Le set
constructeur doit consommer tout le générateur. Mais le comportement de court-circuit de all
garantit que seule une petite partie du générateur doit être consommée, c'est donc plus rapide qu'un test de sous-ensemble de quatre ordres de grandeur .
C'est un exemple extrême, certes. Mais comme cela le montre, vous ne pouvez pas supposer qu'une approche ou une autre sera plus rapide dans tous les cas.
The Upshot
La plupart du temps, la conversion container
en un ensemble en vaut la peine, du moins si tous ses éléments sont hachables. C'est parce que in
pour les ensembles est O (1), tandis que in
pour les séquences est O (n).
D'un autre côté, l'utilisation de tests de sous-ensembles n'en vaut probablement la peine que parfois. Faites-le certainement si vos éléments de test sont déjà stockés dans un ensemble. Sinon, ce all
n'est qu'un peu plus lent et ne nécessite aucun stockage supplémentaire. Il peut également être utilisé avec de gros générateurs d'objets, et fournit parfois une accélération massive dans ce cas.
set(['a', 'b']) <= set(['b','a','foo','bar'])
est une autre façon d'épeler la même chose, et semble "mathier".{'a', 'b'} <= {'b','a','foo','bar'}