Python 3 per a no programadors/Expressions booleanes

Aquí hi ha un petit exemple d'expressions booleanes:

a = 6
b = 7
c = 42
print(1, a == 6)
print(2, a == 7)
print(3, a == 6 and b == 7)
print(4, a == 7 and b == 7)
print(5, not a == 7 and b == 7)
print(6, a == 7 or b == 7)
print(7, a == 7 or b == 6)
print(8, not (a == 7 and b == 6))
print(9, not a == 7 and b == 6)

L'execució d'aquest codi origina la següent resposta:

1 True
2 False
3 True
4 False
5 True
6 True
7 False
8 True
9 False

Què és el que passa? El programa consisteix en un seguit de valoracions print) de declaracions. Cada declaració print retorna un número i una expressió. El número serveix per ajudar-nos a fer el seguiment de quina resposta correspon a cada declaració. Vegeu que cada expressió es resol amb una resposta que és False (Fals) o True (Veritat). En Python false també es pot escriure amb un 0 i true amb un 1.

Les línies:

print(1, a == 6)
print(2, a == 7)

donen com a resposta True i False respectivament ja que la primera expressió és veritat i la segona és falsa. La tercera resposta, print(3, a == 6 and b == 7), és una mica diferent. L'operador and condiciona a que ambdues expressions siguin veritables perquè el valor del conjut de l'expressió sigui veritable. Si una o les dues expressions són False aleshores el resultat de l'expressió serà False. La línia següent, print(4, a == 7 and b == 7), permet comprovar que si una part d'una expressió amb un and és falsa, aleshores el conjunt és fals. El comportament de l'operador and es pot resumir de la següent manera:

expressió resultat
true and true true
true and false false
false and true false
false and false false

Vegeu que si la primera expressió és falsa Python ja no comprova la segona expressió perquè el conjunt serà fals. Proveu d'executar False and print("Hi") i compareu-ho amb l'execució de True and print("Hi") El terme tècnic per això és Evaluació a curt circuit

La línia següent, print(5, not a == 7 and b == 7), fa servir l'operador not. not simplemet retorna l'oposat de l'expressió. (L'expressió es podria reescriure com a print(5, a != 7 and b == 7)).

Aquesta és la taula:

expressió resultat
not true false
not false true

Les dues línies següents, print(6, a == 7 or b == 7) i print(7, a == 7 or b == 6), fan servir l'operador or. L'operador or retorna true si la primera expressió és true, o si la segona expressió és true o ambdues són true. Si cap de les expressions és true aleshores retorna false. Aquesta és la taula:

expressió resultat
true or true true
true or false true
false or true true
false or false false

Vegeu que si la primera expressió és true aleshores Python ja no comprova la segona expressió perquè ja sap que el conjunt de l'expressió serà true (per l'operador or). Això funciona de manera que or és true si com a mínim algun dels elements de l'expressió és cert (true). La primera part és certa (true) de manera que la segona part podria ser certa o falsa, però el conjunt de l'expressió continuaria essent cert (perquè la condició és que algun sigui cert).

Les dues línies següents, print(8, not (a == 7 and b == 6)) i print(9, not a == 7 and b == 6), permeten comprovar que els parèntesis es poden fer servir per agrupar expressions i forçar que s'evaluï primer una de les parts. Vegeu que els parèntesis canvien el resultat de l'expressió de fals a cert. Això passa perquè els parèntesis fan que s'apliqui el not a tota l'expressió en comptes d'aplicar-se només a la porció de l'expressió a == 7.


A continuació es mostra un exemple d'utilització d'una expressió booleana:

llista = ["Vida", "Univers", "Tot", "Jack", "Jill", "Vida", "Jill"]

# fa una còpia de la llista. Vegeu el capítol "Més sobre llistes" per veure què significa [:].
copia = llista[:]
# ordena la còpia
copia.sort()
prev = copia[0]
del copia[0]

comptador = 0

# Avança per la llista per trobar una coincidència
while comptador < len(copia) and copia[comptador] != prev:
    prev = copia[comptador]
    comptador = comptador + 1

# Si no s'ha trobat cap coincidència aleshores comptador no pot ser < len
# ja que el bucle while continua mentre comptador és < len
# i no es troba cap coincidència

if comptador < len(copia):
    print("Primera coincidència:", prev)

L'execució d'aquest codi origina la següent resposta:

Primera coincidència: Jill

Aquest programa el que fa és anar comprovant determinades condicions while comptador < len(copia) i copia[comptador] no és igual a prev. Si comptador és més gran que el darrer index de copia o bé copia[comptador] és igual a prev aleshores l'and deixa de ser cert i surt del bucle. L'if simplement fa una comprovació per assegurar que el while ha sortit perquè s'ha trobat una coincidència.

L'altra característica de l'and es fa servir en aquest exemple. Si mireu la taula per l'and podeu veure que la tercera entrada és "false and false". Si comptador >= len(copia) (en altres paraules comptador < len(copia) és false) aleshores mai es mirarà copia[comptador]. Això és així perquè Python sap que si el primer és fals aleshores ambdós no poden ser veritat. Això es coneix com a curt circuit i és útil si la segona meitatde l'and pot causar un error si hi ha alguna cosa que no és correcta. S'ha fet servir la primera expressió (comptador < len(copia)) per comprovar i veure si comptador era un índex vàlid per a copia. (Si teniu algun dubte que això sigui així podeu suprimir les coicidències "Jill" i "Life", comprovar que encara funcionai aleshores capgirar l'ordre de comptador < len(copia) and copia[comptador] != prev perquè sigui copia[comptador] != prev and comptador < len(copia).)

Les expressions booleanes es poden fer servir quan és necesasri comprovar dues o més coses diferents a la vegada.

Notes sobre Operadors Booleans

modifica

Un error comú entre els programadors més novells és la mala interpretació sobre la manera com funcionen els operadors booleans, que ve de la manera com l'intèrpret de python llegeix aquestes expressions. Per exemple, després de l'aprenentatge inicial sobre les declaracions "and " i "or", es pot assumir que l'expressió x == ('a' or 'b') comprovarà si la variable x és equivalent a una de les dues cadenes 'a' o 'b'. Això no és així. Per veure què és el que realment passa, obriu una sessió amb l'intèrpret i introduïu les expressions següents:

>>> 'a' == ('a' or 'b')
>>> 'b' == ('a' or 'b')
>>> 'a' == ('a' and 'b')
>>> 'b' == ('a' and 'b')

I aquest serà el resultat intuïtiu:

>>> 'a' == ('a' or 'b')
True
>>> 'b' == ('a' or 'b')
False
>>> 'a' == ('a' and 'b')
False 
>>> 'b' == ('a' and 'b')
True

En aquest punt, els operadors and i or semblen erronis. No sembla que tingui sentit que, per les dues primeres expressions, 'a' sigui equivalent a 'a' o 'b' mentre que 'b' no ho sigui. A més a més, no té cap sentit que 'b' sigui equivalent a 'a' i 'b'. Després d'examinar què fa l'intèrpret amb els operadors booleans, aquests resultats fan de fet exactament el que se'ls demana, que no és el mateix que el que penses que estàs demanant.

Quan l'intèrpret Python mira l'expressió or, agafa la primera declaració i fa la comprovació per veure si és certa (true). Si la primera declaració és certa (true), aleshores Python retorna aquest valor de l'objecte sense comprovar la segona declaració. Això és així perquè per una expressió or, el conjunt és cert si un dels valors és cert; el programa no té per què molestar-se a mirar la segona declaració. D'altra banda, si el primer valor és evaluat com a fals Python comprova la segona meitat i retorna aquest valor. Aquesta segona meitat determina el valor veritable del conjunt de l'expressió des del moment en què la primera meitat era falsa. Aquesta "mandra" per part de l'intèrpret s'anomena "curt circuit" i és una manera comuna d'avaluar les expressions booleanes en molts llenguatges de programació.

De manera similar, per una expressió and, Python fa servir la tècnica del curt circuit per accelerar l'avaluació del valor veritable. Si la primera declaració és falsa aleshores el conjunt ha de ser fals, de manera que retorna aquest valor. D'altra banda si el primer valor és veritat (true), aleshores comprova el segon i retorna aquest valor.

Una cosa a tenir en compte en aquest punt és que l'expressió booleana retorna un valor indicant True o False, però Python considera un número de coses diferents per assignar-hi un valor cert. Per comprovar el valor cert de qualsevol objecte donat x, es pot fer servir la funció bool(x) per veure els seus valors veritables.

A continuació es mostra una taula amb exemples de valors veritables de diversos objectes:

True False
True False
1 0
Números diferents de zero La cadena 'None'
Cadenes que no són buides Cadenes buides
Llistes que no són buides Llistes buides
Diccionaris que no són buits Diccionaris buits

Ara és possible entendre el resultats confusos que s'han obtingut fent la prova de les expressions booleanes anteriors. Fem un cop d'ull al que "veu" l'intèrpret tal com va llegint el codi:

Primer cas:

>>> 'a' == ('a' or 'b')  # Fixem-nos primer en els parèntesis, de manera que evaluem l'expressió "('a' or 'b')"
                           # 'a' és una cadena que no és buida, de manera que el primer valor és True
                           # Retorna aquest primer valor: 'a'
>>> 'a' == 'a'           # la cadena 'a' és equivalent a la cadena 'a', per la qual cosa l'expressió és True
True

Segon cas:

>>> 'b' == ('a' or 'b')  # Fixem-nos primer en els parèntesis, de manera que evaluem l'expressió "('a' or 'b')"
                           # 'a' és una cadena que no és buida, de manera que el primer valor és True
                           # Retorna aquest primer valor: 'a'
>>> 'b' == 'a'           # la cadena 'b' no és equivalent a la cadena 'a', per la qual cosa l'expressió és False
False 

Tercer cas:

>>> 'a' == ('a' and 'b') # Fixem-nos primer en els parèntesis, de manera que evaluem l'expressió "('a' and 'b')"
                           # 'a' és una cadena que no és buida, de manera que el primer valor és True; examina el segon valor
                           # 'b' és una cadena que no és buida, de manera que el segon valor és True
                           # Retorna aquest segon valor com a resultat de tota l'expressió: 'b'
>>> 'a' == 'b'           # la cadena 'a' no és equivalent a la cadena 'b', i per tant l'expressió és False
False

Quart cas:

>>> 'b' == ('a' and 'b') # Fixem-nos primer en els parèntesis, de manera que evaluem l'expressió "('a' and 'b')"
                           # 'a' és una cadena que no és buida, de manera que el primer valor és True; examina el segon valor
                           # 'b' és una cadena que no és buida, de manera que el segon valor és True
                           # Retorna aquest segon valor com a resultat de tota l'expressió: 'b'
>>> 'b' == 'b'           # la cadena 'b' és equivalent a la cadena 'b', i per tant l'expressió és True
True 

Per la qual cosa Python estava fent realment la seva feina quan ens donava aquells resultats aparentment sorprenents. Tal com s'ha esmentant anteriorment, el que és important e´s reconèixer quin serà el valor que retornarà l'expressió quan s'avaluï, perquè no sempre és obvi.

Tornant enrere cap a les expressions inicials, així és com s'hauria d'haver escrit per obtenir el que amb tota probabilitat volíeu obtenir:

>>> 'a' == 'a' or 'a' == 'b' 
True
>>> 'b' == 'a' or 'b' == 'b' 
True
>>> 'a' == 'a' and 'a' == 'b' 
False
>>> 'b' == 'a' and 'b' == 'b'
False

Quan s'avaluen aquestes comparacions retornen valors veritables en termes de True o False, no pas cadenes de text, i per tant obtenim els resultats adients.

Exemples

modifica

password1.py

## Aquest programa pregunta a l'usuari un nom i una paraula clau.
# Aleshores comprova que l'usuari tingui accés.

nom = input("Quin és el vostre nom? ")
paraula_clau = input("Quina és la paraula clau? ")
if nom == "Jordi" and paraula_clau == "Divendres":
    print("Benvingut Jordi")
elif nom == "Joan" and paraula_clau == "Saturn":
    print("Benvingut Joan")
else:
    print("No sabem qui sou.")

L'exemple funciona de la següent manera

Quin és el vostre nom? Jordi
Quina és la paraula clau? Divendres
Benvingut Jordi
Quin és el vostre nom? Miquel
Quina és la paraula clau? Banc
No sabem qui sou.

Exercicis

modifica

Escriure un programa perquè un usuari encerti el vostre nom, però que només tingui tres oportunitats abans que el programa surti.