'et' (booléen) vs '&' (bit à bit) - Pourquoi une différence de comportement entre les listes et les tableaux numpy?


142

Qu'est-ce qui explique la différence de comportement des opérations booléennes et binaires sur les listes par rapport aux tableaux NumPy?

Je ne sais pas comment utiliser correctement &vs anden Python, illustré dans les exemples suivants.

mylist1 = [True,  True,  True, False,  True]
mylist2 = [False, True, False,  True, False]

>>> len(mylist1) == len(mylist2)
True

# ---- Example 1 ----
>>> mylist1 and mylist2
[False, True, False, True, False]
# I would have expected [False, True, False, False, False]

# ---- Example 2 ----
>>> mylist1 & mylist2
TypeError: unsupported operand type(s) for &: 'list' and 'list'
# Why not just like example 1?

>>> import numpy as np

# ---- Example 3 ----
>>> np.array(mylist1) and np.array(mylist2)
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
# Why not just like Example 4?

# ---- Example 4 ----
>>> np.array(mylist1) & np.array(mylist2)
array([False,  True, False, False, False], dtype=bool)
# This is the output I was expecting!

Cette réponse et cette réponse m'ont aidé à comprendre qu'il ands'agit d'une opération booléenne, mais d' &une opération au niveau du bit.

J'ai lu sur les opérations au niveau du bit pour mieux comprendre le concept, mais j'ai du mal à utiliser ces informations pour donner un sens à mes 4 exemples ci-dessus.

Exemple 4 m'a conduit à ma sortie désirée, de sorte que c'est très bien, mais je suis encore confus quand / comment / pourquoi je devrais utiliser andvs &. Pourquoi les listes et les tableaux NumPy se comportent-ils différemment avec ces opérateurs?

Quelqu'un peut-il m'aider à comprendre la différence entre les opérations booléennes et binaires pour expliquer pourquoi ils gèrent différemment les listes et les tableaux NumPy?


2
Dans Numpy, il y a np.bitwise_and()et np.logical_and()et des amis pour éviter toute confusion.
Dietrich

1
Dans l'exemple 1, mylist1 and mylist2ne produit pas le même résultat que mylist2 and mylist1, puisque ce qui est renvoyé est la deuxième liste comme indiqué par delnan.
user2015487

Réponses:


114

andteste si les deux expressions sont logiquement Truetandis que &(lorsqu'il est utilisé avec True/ Falsevalues) teste si les deux le sont True.

En Python, les objets intégrés vides sont généralement traités comme logiquement Falsetandis que les objets intégrés non vides le sont logiquement True. Cela facilite le cas d'utilisation courant où vous voulez faire quelque chose si une liste est vide et autre chose si la liste ne l'est pas. Notez que cela signifie que la liste [False] est logiquement True:

>>> if [False]:
...    print 'True'
...
True

Ainsi, dans l'exemple 1, la première liste est non vide et donc logiquement True, donc la valeur de vérité de andest la même que celle de la deuxième liste. (Dans notre cas, la deuxième liste n'est pas vide et donc logiquement True, mais identifier cela nécessiterait une étape de calcul inutile.)

Par exemple 2, les listes ne peuvent pas être combinées de manière significative de manière binaire car elles peuvent contenir des éléments arbitraires contrairement à. Les éléments pouvant être combinés au niveau du bit incluent: les vrais et les faux, les entiers.

Les objets NumPy, en revanche, prennent en charge les calculs vectorisés. Autrement dit, ils vous permettent d'effectuer les mêmes opérations sur plusieurs éléments de données.

L'exemple 3 échoue car les tableaux NumPy (de longueur> 1) n'ont pas de valeur de vérité car cela évite la confusion de la logique vectorielle.

L'exemple 4 est simplement une andopération bit vectorisée .

Conclusion

  • Si vous ne travaillez pas avec des tableaux et n'effectuez pas de manipulations mathématiques d'entiers, vous le souhaitez probablement and.

  • Si vous avez des vecteurs de valeurs de vérité que vous souhaitez combiner, utilisez numpyavec &.


27

À propos list

D'abord un point très important, d'où tout découlera (j'espère).

En Python ordinaire, ce listn'est en aucun cas spécial (sauf avoir une syntaxe mignonne pour la construction, ce qui est principalement un accident historique). Une fois qu'une liste [3,2,6]est faite, c'est à toutes fins utiles juste un objet Python ordinaire, comme un nombre 3, un ensemble {3,7}ou une fonction lambda x: x+5.

(Oui, il prend en charge la modification de ses éléments, et il prend en charge l'itération, et bien d'autres choses, mais c'est exactement ce qu'est un type: il prend en charge certaines opérations, sans en supporter d'autres. Int prend en charge l'élévation à une puissance, mais ce n'est pas le cas rendez-le très spécial - c'est exactement ce qu'est un int. lambda prend en charge les appels, mais cela ne le rend pas très spécial - c'est à cela que sert lambda, après tout :).

À propos and

andn'est pas un opérateur (vous pouvez l'appeler "opérateur", mais vous pouvez également appeler "for" un opérateur :). Les opérateurs en Python sont (implémentés via) des méthodes appelées sur des objets d'un certain type, généralement écrits dans le cadre de ce type. Il n'y a aucun moyen pour une méthode de tenir une évaluation de certains de ses opérandes, mais andpeut (et doit) le faire.

La conséquence de cela est que andcela ne peut pas être surchargé, tout comme forne peut pas être surchargé. Il est complètement général et communique via un protocole spécifié. Ce que vous pouvez faire est de personnaliser votre partie du protocole, mais cela ne signifie pas que vous pouvez modifier andcomplètement le comportement de . Le protocole est:

Imaginez Python interprétant "a et b" (cela ne se produit pas littéralement de cette façon, mais cela aide à comprendre). Quand il s'agit de "et", il regarde l'objet qu'il vient d'évaluer (a), et lui demande: êtes-vous vrai? ( PAS : l'êtes-vous True?) Si vous êtes un auteur de la classe de a, vous pouvez personnaliser cette réponse. Si arépond «non», and(saute complètement b, il n'est pas évalué du tout, et) dit: aest mon résultat ( PAS : faux est mon résultat).

Si ane répond pas, andlui demande: quelle est votre longueur? (Encore une fois, vous pouvez personnaliser cela en tant qu'auteur de ala classe de). Si la aréponse est 0, andfait la même chose que ci-dessus - considère que c'est faux ( PAS faux), saute b et donne acomme résultat.

Si arépond autre chose que 0 à la deuxième question ("quelle est votre longueur"), ou s'il ne répond pas du tout, ou s'il répond "oui" à la première ("êtes-vous vrai"), andévalue b, et dit: best mon résultat. Notez qu'il ne pose PASb de questions.

L'autre façon de dire tout cela est que a and bc'est presque la même chose que b if a else a, sauf que a n'est évalué qu'une seule fois.

Maintenant, asseyez-vous pendant quelques minutes avec un stylo et du papier, et convainquez-vous que lorsque {a, b} est un sous-ensemble de {True, False}, cela fonctionne exactement comme vous vous attendez des opérateurs booléens. Mais j'espère vous avoir convaincu que c'est beaucoup plus général, et comme vous le verrez, beaucoup plus utile de cette façon.

Mettre ces deux ensemble

J'espère que vous comprenez votre exemple 1. andne se soucie pas si mylist1 est un nombre, une liste, un lambda ou un objet d'une classe Argmhbl. Il se soucie juste de la réponse de mylist1 aux questions du protocole. Et bien sûr, mylist1 répond à 5 à la question sur la longueur, donc et renvoie mylist2. Et c'est tout. Cela n'a rien à voir avec les éléments de mylist1 et mylist2 - ils n'entrent nulle part dans l'image.

Deuxième exemple: &surlist

En revanche, &c'est un opérateur comme les autres, comme +par exemple. Il peut être défini pour un type en définissant une méthode spéciale sur cette classe. intle définit comme "et" au niveau du bit, et bool le définit comme "et" logique, mais ce n'est qu'une option: par exemple, les ensembles et certains autres objets comme les vues de clés dict le définissent comme une intersection d'ensemble. listne le définit tout simplement pas, probablement parce que Guido n'a pensé à aucun moyen évident de le définir.

engourdi

De l'autre côté: -D, les tableaux numpy sont spéciaux, ou du moins ils essaient de l'être. Bien sûr, numpy.array est juste une classe, il ne peut anden aucun cas remplacer , donc il fait la meilleure chose suivante: quand on lui demande "êtes-vous vrai", numpy.array lève une ValueError, en disant effectivement "veuillez reformuler la question, mon la vision de la vérité ne rentre pas dans votre modèle ». (Notez que le message ValueError ne parle pas and- car numpy.array ne sait pas qui lui pose la question; il parle juste de la vérité.)

Car &, c'est une histoire complètement différente. numpy.array peut le définir comme il le souhaite, et il définit de manière &cohérente avec les autres opérateurs: point par point. Donc, vous obtenez enfin ce que vous voulez.

HTH,


23

Les opérateurs booléens de court-circuit ( and, or) ne peuvent pas être remplacés car il n'y a pas de moyen satisfaisant de le faire sans introduire de nouvelles fonctionnalités de langage ou sacrifier le court-circuit. Comme vous le savez peut-être ou non, ils évaluent le premier opérande pour sa valeur de vérité et, en fonction de cette valeur, évaluent et renvoient le deuxième argument, ou n'évaluent pas le deuxième argument et renvoient le premier:

something_true and x -> x
something_false and x -> something_false
something_true or x -> something_true
something_false or x -> x

Notez que le (résultat de l'évaluation de) l'opérande réel est renvoyé, et non sa valeur de vérité.

Le seul moyen de personnaliser leur comportement est de remplacer __nonzero__(renommé __bool__en Python 3), de sorte que vous puissiez affecter l'opérande retourné, mais pas retourner quelque chose de différent. Les listes (et autres collections) sont définies comme étant «véridiques» lorsqu'elles contiennent quelque chose du tout, et «fausses» lorsqu'elles sont vides.

Les tableaux NumPy rejettent cette notion: pour les cas d'utilisation auxquels ils visent, deux notions différentes de vérité sont communes: (1) si un élément est vrai, et (2) si tous les éléments sont vrais. Étant donné que ces deux éléments sont complètement (et silencieusement) incompatibles, et qu'aucun n'est clairement plus correct ou plus courant, NumPy refuse de deviner et vous oblige à utiliser explicitement .any()ou .all().

&et |(et not, au fait) peuvent être entièrement annulés, car ils ne court-circuitent pas. Ils peuvent renvoyer n'importe quoi lorsqu'ils sont surchargés, et NumPy en fait bon usage pour effectuer des opérations élémentaires, comme ils le font avec pratiquement toute autre opération scalaire. Les listes, en revanche, ne diffusent pas d'opérations sur leurs éléments. Tout comme mylist1 - mylist2ne veut rien dire et mylist1 + mylist2signifie quelque chose de complètement différent, il n'y a pas d' &opérateur pour les listes.


3
Un exemple particulièrement intéressant de ce que cela peut produire est l’ [False] or [True]évaluation [False]et l’ [False] and [True]évaluation [True].
Rob Watts

16

Exemple 1:

C'est ainsi que fonctionne l' opérateur and .

x et y => si x est faux, alors x , sinon y

Donc, en d'autres termes, puisque mylist1n'est pas False, le résultat de l'expression est mylist2. (Seules les listes vides sont évaluées False.)

Exemple 2:

L' &opérateur est pour un bit et, comme vous le mentionnez. Les opérations au niveau du bit ne fonctionnent que sur les nombres. Le résultat de a & b est un nombre composé de 1 en bits qui valent 1 à la fois en a et en b . Par exemple:

>>> 3 & 1
1

Il est plus facile de voir ce qui se passe en utilisant un littéral binaire (mêmes nombres que ci-dessus):

>>> 0b0011 & 0b0001
0b0001

Les opérations au niveau du bit sont similaires dans leur concept aux opérations booléennes (vérité), mais elles ne fonctionnent que sur des bits.

Donc, compte tenu de quelques déclarations sur ma voiture

  1. Ma voiture est rouge
  2. Ma voiture a des roues

Le "et" logique de ces deux déclarations est:

(ma voiture est-elle rouge?) et (la voiture a-t-elle des roues?) => valeur logique vraie de fausse valeur

Les deux sont vrais, du moins pour ma voiture. La valeur de l'énoncé dans son ensemble est donc logiquement vraie.

Le "et" au niveau du bit de ces deux déclarations est un peu plus nébuleux:

(la valeur numérique de l'instruction 'ma voiture est rouge') & (la valeur numérique de l'instruction 'ma voiture a des roues') => nombre

Si python sait comment convertir les instructions en valeurs numériques, alors il le fera et calculera le bit et des deux valeurs. Cela peut vous amener à croire que &c'est interchangeable avec and, mais comme dans l'exemple ci-dessus, ce sont des choses différentes. De plus, pour les objets qui ne peuvent pas être convertis, vous obtiendrez simplement un fichier TypeError.

Exemple 3 et 4:

Numpy implémente des opérations arithmétiques pour les tableaux:

Les opérations arithmétiques et de comparaison sur ndarrays sont définies comme des opérations élément par élément et donnent généralement des objets ndarray comme résultats.

Mais n'implémente pas d'opérations logiques pour les tableaux, car vous ne pouvez pas surcharger les opérateurs logiques en python . C'est pourquoi l'exemple trois ne fonctionne pas, mais l'exemple quatre fonctionne.

Donc, pour répondre à votre question andvs &: utilisez and.

Les opérations au niveau du bit sont utilisées pour examiner la structure d'un nombre (quels bits sont définis, quels bits ne sont pas définis). Ce type d'informations est principalement utilisé dans les interfaces de système d'exploitation de bas niveau ( bits d'autorisation Unix , par exemple). La plupart des programmes python n'auront pas besoin de le savoir.

Les opérations logiques ( and, or, not), cependant, sont utilisés tout le temps.


14
  1. En Python, une expression de X and Yretours Y, étant donné que bool(X) == Trueou l'un de Xou Yévalué à False, par exemple:

    True and 20 
    >>> 20
    
    False and 20
    >>> False
    
    20 and []
    >>> []
  2. L'opérateur au niveau du bit n'est tout simplement pas défini pour les listes. Mais il est défini pour les entiers - fonctionnant sur la représentation binaire des nombres. Considérez 16 (01000) et 31 (11111):

    16 & 31
    >>> 16
  3. NumPy n'est pas un médium, il ne sait pas si vous voulez dire que par exemple [False, False]devrait être égal à Truedans une expression logique. En cela, il remplace un comportement Python standard, qui est: "Toute collection vide avec len(collection) == 0est False".

  4. Probablement un comportement attendu de l'opérateur & des tableaux de NumPy.


False et 20 renvoie False
Rahul

4

Pour le premier exemple et base sur la doc de django
Il retournera toujours la deuxième liste, en effet une liste non vide est considérée comme une valeur True pour Python donc python renvoie la 'dernière' valeur True donc la deuxième liste

In [74]: mylist1 = [False]
In [75]: mylist2 = [False, True, False,  True, False]
In [76]: mylist1 and mylist2
Out[76]: [False, True, False, True, False]
In [77]: mylist2 and mylist1
Out[77]: [False]

4

Les opérations avec une liste Python opèrent sur la liste . list1 and list2vérifiera si list1est vide, et retournera list1si c'est le cas, et list2si ce n'est pas le cas. list1 + list2ajoutera list2à list1, donc vous obtenez une nouvelle liste avec des len(list1) + len(list2)éléments.

Les opérateurs qui n'ont de sens que lorsqu'ils sont appliqués par élément, tels que &lever a TypeError, car les opérations par élément ne sont pas prises en charge sans boucler les éléments.

Les tableaux Numpy prennent en charge les opérations élémentaires . array1 & array2calculera le bit à bit ou pour chaque élément correspondant dans array1et array2. array1 + array2calculera la somme de chaque élément correspondant dans array1et array2.

Cela ne fonctionne pas pour andet or.

array1 and array2 est essentiellement un raccourci pour le code suivant:

if bool(array1):
    return array2
else:
    return array1

Pour cela, vous avez besoin d'une bonne définition de bool(array1). Pour les opérations globales comme celles utilisées sur les listes Python, la définition est que bool(list) == Truesi listn'est pas vide, et Falsesi elle est vide. Pour les opérations élément par élément de numpy, il y a une certaine homonymie s'il faut vérifier si un élément est évalué à Trueou si tous les éléments sont évalués à True. Parce que les deux sont sans doute corrects, numpy ne devine pas et lève un ValueErrorwhen bool()est (indirectement) appelé sur un tableau.


0

Bonne question. Similaire à l'observation que vous avez sur les exemples 1 et 4 (ou devrais-je dire 1 & 4 :)) sur les opérateurs logiques au niveau du andbit &, j'ai expérimenté sur l' sumopérateur. Le numpy sumet le py sumse comportent également différemment. Par exemple:

Supposons que "mat" soit un tableau numpy 5x5 2d tel que:

array([[ 1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15],
       [16, 17, 18, 19, 20],
       [21, 22, 23, 24, 25]])

Puis numpy.sum (mat) donne la somme totale de la matrice entière. Alors que la somme intégrée de Python telle que sum (mat) totalise le long de l'axe uniquement. Voir ci-dessous:

np.sum(mat)  ## --> gives 325
sum(mat)     ## --> gives array([55, 60, 65, 70, 75])
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.