Qu'est-ce qui explique la différence de comportement d'opérations booléennes et binaires sur les tableaux vs tableaux NumPy?
Je suis confus quant à l'utilisation appropriée de _&
_ vs and
en Python, illustrée 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'a aidé à comprendre que and
est une opération booléenne mais &
est un opération au niveau du bit.
J'ai lu opérations au niveau des bits pour mieux comprendre le concept, mais j'ai du mal à utiliser cette information pour donner un sens à mes 4 exemples ci-dessus.
L'exemple 4 m'a conduit à la sortie souhaitée, ce qui est correct, mais je ne comprends toujours pas quand/comment/pourquoi utiliser and
vs _&
_. 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 au niveau des bits pour expliquer pourquoi ils gèrent les listes et les tableaux NumPy différemment?
and
teste si les deux expressions sont logiquement True
tandis que &
(lorsqu'il est utilisé avec True
/False
valeurs) vérifie si les deux sont True
.
En Python, les objets intégrés vides sont généralement traités comme des éléments logiques False
, tandis que les éléments intégrés non vides sont logiquement True
. Cela facilite le cas d'utilisation courant dans lequel 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
, de sorte que la valeur de vérité de and
est identique à celle de la deuxième liste. (Dans notre cas, la deuxième liste n'est pas vide et donc logiquement True
, mais cela nécessiterait une étape de calcul inutile.)
Par exemple 2, les listes ne peuvent pas être combinées de manière significative au niveau du bit, car elles peuvent contenir des éléments différents et arbitraires. Les choses qui peuvent être combinées bit à bit incluent: les indices et les faux, les entiers.
Les objets NumPy, en revanche, prennent en charge les calculs vectorisés. C'est-à-dire qu'ils vous permettent d'effectuer les mêmes opérations sur plusieurs 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 logique basée sur les vecteurs.
L'exemple 4 est simplement une opération de bit vectorisé and
.
Résultat final
Si vous ne traitez pas de tableaux et n'effectuez pas de manipulations mathématiques d'entiers, vous voulez probablement and
.
Si vous souhaitez combiner des vecteurs de valeurs de vérité, utilisez numpy
avec &
.
Les opérateurs booléens en court-circuit (and
, or
) ne peuvent pas être annulés car il n'y a pas de moyen satisfaisant de le faire sans introduire de nouvelles fonctionnalités de langage ou sans 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, évalue et renvoie le deuxième argument ou n'évalue pas le deuxième argument et renvoie 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’opérande réel est renvoyé, et non sa valeur de vérité.
La seule façon de personnaliser leur comportement est de remplacer __nonzero__
(renommé en __bool__
dans Python 3], afin de pouvoir affecter l'opérande renvoyé, mais ne pas renvoyer un élément différent. Les listes (et autres collections) sont définies comme "vérités" lorsqu'elles contiennent quoi que ce soit et "falsey" lorsqu'elles sont vides.
Les tableaux NumPy rejettent cette notion: pour les cas d'utilisation visés, 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. Puisque ces deux sont totalement (et silencieusement) incompatibles, et que ni l'un ni l'autre n'est clairement plus correct ou plus commun, NumPy refuse de deviner et vous oblige à utiliser explicitement .any()
ou .all()
.
&
et |
(et not
, en passant) peut être totalement annulé, car ils ne court-circuitent pas. Ils peuvent renvoyer n'importe quoi lorsqu'ils sont annulés, et NumPy en fait bon usage pour effectuer des opérations élément par élément, comme c'est le cas pour pratiquement toute autre opération scalaire. Les listes, en revanche, ne diffusent pas d'opérations sur leurs éléments. Tout comme mylist1 - mylist2
ne veut rien dire et mylist1 + mylist2
signifie quelque chose de complètement différent, il n'y a pas d'opérateur &
pour les listes.
list
D'abord un point très important, d'où tout va suivre (j'espère).
En Python ordinaire, list
n'a aucune particularité (à l'exception de la syntaxe mignonne pour la construction, qui est principalement un accident historique). Une fois qu'une liste [3,2,6]
est créée, il ne s'agit pratiquement que d'un objet Python ordinaire, semblable à un nombre 3
, défini {3,7}
ou à une fonction lambda x: x+5
.
(Oui, il prend en charge la modification de ses éléments, ainsi que l'itération et bien d'autres choses, mais c'est exactement ce que l'on appelle un type: il prend en charge certaines opérations, mais pas d'autres. Int supporte l'augmentation du pouvoir, mais cela ne fonctionne pas. lambda prend en charge l’appel, mais cela ne le rend pas très spécial - c’est à cela que sert lambda, après tout :).
and
and
n'est pas un opérateur (vous pouvez l'appeler "opérateur", mais vous pouvez aussi appeler "pour" un opérateur :). Les opérateurs dans Python sont des méthodes (implémentées via) appelées sur des objets d'un type donné, généralement écrites dans le cadre de ce type. Il n’existe aucun moyen pour une méthode d’organiser une évaluation de certains de ses opérandes, mais and
peut (et doit) le faire.
La conséquence en est que and
ne peut pas être surchargé, tout comme for
ne peut pas être surchargé. Il est complètement général et communique via un protocole spécifié. Ce que vous pouvez faites, c'est personnaliser votre partie du protocole, mais cela ne signifie pas que vous pouvez modifier complètement le comportement de and
. Le protocole est:
Imaginez Python interpréter "a et b" (cela ne se produit pas littéralement de cette façon, mais cela aide à la compréhension). Quand il s'agit de "et", il regarde l'objet qu'il vient d'évaluer (a) et lui demande: es-tu vrai? (NOT: êtes-vous True
?) Si vous êtes auteur d'une classe, vous pouvez personnaliser cette réponse. Si a
répond "non", and
(saute b complètement, il n'est pas évalué du tout et) dit: a
est mon résultat (PAS =: False est mon résultat).
Si a
ne répond pas, and
demande: quelle est votre longueur? (Encore une fois, vous pouvez personnaliser cela en tant qu'auteur de la classe a
). Si a
répond à 0, and
fait la même chose que ci-dessus - le considère comme faux (PAS False), saute b et donne a
comme résultat.
Si a
ré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 question ("êtes-vous vrai") , and
évalue b et dit: b
est mon résultat. Notez que cela PAS demande à b
de poser des questions.
L'autre façon de dire tout cela est que a and b
est presque identique à b if a else a
, sauf que a n'est évalué qu'une fois.
Maintenant, asseyez-vous quelques minutes avec un stylo et du papier et assurez-vous que, lorsque {a, b} est un sous-ensemble de {True, False}, il fonctionne exactement comme vous le souhaiteriez 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.
Maintenant, j'espère que vous comprenez votre exemple 1. and
ne se soucie pas de savoir si mylist1 est un nombre, une liste, un lambda ou un objet de la classe Argmhbl. Il se soucie simplement de la réponse de mylist1 aux questions du protocole. Et bien sûr, mylist1 répond 5 à la question sur la longueur, donc retourne mylist2. Et c'est tout. Cela n'a rien à voir avec des éléments de mylist1 et mylist2 - ils n'entrent pas dans l'image nulle part.
&
sur list
Par contre, &
est un opérateur comme un autre, comme +
par exemple. Il peut être défini pour un type en définissant une méthode spéciale sur cette classe. int
le définit comme "et" au niveau des bits, et bool le définit comme des "et" logiques, mais ce n'est qu'une option: par exemple, les ensembles et certains autres objets tels que les vues des clés dict le définissent comme une intersection d'ensemble. list
ne le définit tout simplement pas, probablement parce que Guido n'a pas pensé à un moyen évident de le définir.
Sur l’autre jambe: -D, les tableaux numpy sont spéciaux, ou du moins, ils essaient de l’être. Bien sûr, numpy.array n’est qu’une classe, il ne peut en aucun cas remplacer and
, de sorte qu’il a la meilleure chose à faire. reformulez la question, ma vision de la vérité ne correspond pas à votre modèle ". (Notez que le message ValueError ne parle pas de and
- car numpy.array ne sait pas qui lui pose la question; ça parle juste de vérité.)
Pour &
, c'est une histoire complètement différente. numpy.array peut le définir à sa guise, et il définit &
de manière cohérente avec les autres opérateurs: point à point. Donc, vous obtenez enfin ce que vous voulez.
HTH,
Exemple 1:
Voici comment fonctionne l'opérateur et .
x et y => si x est faux, alors x , sinon y
Donc, en d'autres termes, puisque mylist1
n'est pas False
, le résultat de l'expression est mylist2
. (Seulement listes vides évalue à False
.)
Exemple 2:
L'opérateur &
est pour un bitwise et, comme vous le dites. Les opérations sur les bits ne fonctionnent que sur les nombres. Le résultat de a & b est un nombre composé de 1 en bits qui sont 1 dans les deux a et b . Par exemple:
>>> 3 & 1
1
Il est plus facile de voir ce qui se passe avec un littéral binaire (mêmes chiffres que ci-dessus):
>>> 0b0011 & 0b0001
0b0001
Le concept des opérations au niveau des bits est similaire à celui des opérations booléennes (vérité), mais elles ne fonctionnent que sur les bits.
Donc, étant donné quelques déclarations à propos de ma voiture
La logique "et" de ces deux déclarations est:
(ma voiture est-elle rouge?) et (la voiture a-t-elle des roues?) => logique vrai de fausse valeur
Les deux sont vrais, du moins pour ma voiture. Donc, la valeur de l'instruction dans son ensemble est logiquement vrai.
Le bitwise "et" de ces deux déclarations est un peu plus nébuleux:
(la valeur numérique de la déclaration 'ma voiture est rouge') & (la valeur numérique de la déclaration 'ma voiture a des roues') => nombre
Si python sait comment convertir les instructions en valeurs numériques, il le fera et calculera le bitwise et les deux valeurs. Cela peut vous amener à croire que &
est interchangeable avec and
, mais comme dans l'exemple ci-dessus, il s'agit de choses différentes. De plus, pour les objets qui ne peuvent pas être convertis, vous obtiendrez simplement un TypeError
.
Exemples 3 et 4:
Numpy implémente opérations arithmétiques pour les tableaux:
Les opérations arithmétiques et de comparaison sur les ndarrays sont définies comme des opérations élément par élément et donnent généralement des objets ndarray en tant que 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.
Donc, pour répondre à votre question and
vs &
: Utilisez and
.
Les opérations au niveau des bits 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 de permission Unix , par exemple). La plupart des programmes python n'auront pas besoin de le savoir.
Les opérations logiques (and
, or
, not
) sont toutefois utilisées à tout moment.
Dans Python, une expression de X and Y
renvoie Y
, étant donné que bool(X) == True
ou l'un quelconque des X
ou Y
est défini sur False, par exemple:
True and 20
>>> 20
False and 20
>>> False
20 and []
>>> []
L'opérateur binaire n'est tout simplement pas défini pour les listes. Mais il est défini pour les entiers - opérant sur la représentation binaire des nombres. Considérons 16 (01000) et 31 (11111):
16 & 31
>>> 16
NumPy n'est pas un psychique, il ne sait pas si vous voulez dire par exemple que. [False, False]
devrait être égal à True
dans une expression logique. En cela, il remplace un comportement standard Python, qui est le suivant: "Toute collection vide avec len(collection) == 0
est False
".
Probablement un comportement attendu des tableaux et de l'opérateur de NumPy.
Pour le premier exemple et base sur le doc de Django
La deuxième liste sera toujours renvoyée. En effet, une liste non vide est considérée comme une valeur True pour Python ainsi python renvoie la "dernière" valeur True afin que la seconde 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]
Les opérations avec une liste Python fonctionnent sur la liste liste. list1 and list2
vérifie si list1
est vide et renvoie list1
s'il en existe un, et list2
s'il ne l'est pas. list1 + list2
ajoutera list2
à list1
, afin que vous obteniez une nouvelle liste d'éléments len(list1) + len(list2)
.
Les opérateurs qui n'ont de sens que lorsqu'ils sont appliqués élément par élément, tels que &
, lèvent un TypeError
, car les opérations par élément ne sont pas prises en charge sans une boucle entre les éléments.
Les tableaux Numpy prennent en charge les opérations élément par élément. array1 & array2
calculera le bit ou pour chaque élément correspondant dans array1
et array2
. array1 + array2
calculera la somme de chaque élément correspondant dans array1
et array2
.
Cela ne fonctionne pas pour and
et 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 telles qu'utilisées sur les listes Python, la définition est que bool(list) == True
si list
n'est pas vide et False
s'il est vide. Pour les opérations élémentaires de numpy, il existe une certaine ambiguïté quant à savoir si un élément doit être évalué à True
ou si tous les éléments sont évalués à True
. Comme les deux sont vraisemblablement corrects, numpy ne le devine pas et lève un ValueError
lorsque bool()
est appelé (indirectement) sur un tableau.