HOWTO zu den Booleschen Wahrheitswerten in Python

(C) 2016-2021 T.Birnthaler/H.Gottschalk <howtos(at)ostc.de>
              OSTC Open Source Training and Consulting GmbH
              www.ostc.de

$Id: python-boolean.txt,v 1.4 2021/07/28 08:29:35 tsbirn Exp $

Dieses Dokument beschreibt die Boolesche Logik der Werte in Python,
insbesondere die Booleschen Werte True und False sowie den Wert None.

Inhaltsverzeichnis

1) Boolesche Werte
1b) Boolescher Kontext
1c) Zusammenhang mit Zahlen-Datentypen int/float/complex
2) Logische Operatoren
3) Ausdrücke mit logischen Werten
4) Short Cut/Circuit Evaluation
5) Undefinierter Wert None versus definierte Werte
6) Test auf True oder False
6.1) True == 1/1.0 und False == 0/0.0
7) Test auf leeren/gefüllten Container
8) 3-wertige Logik (analog SQL)

1) Boolesche Werte   (Toc)

Doku --> docs.python.org/3/library/stdtypes.html#boolean-values
         docs.python.org/3/library/stdtypes.html#truth

Python kennt einen Booleschen Datentyp bool mit den beiden Werten True und
False, aber interpretiert auch JEDEN anderen Wert JEDES Typs (NoneType, int,
long, float, complex, str, tuple, list, dict, ...) als logischen Wert. Dabei
ist exakt definiert, welche Werte als logisch "falsch" und welche als logisch
"wahr" interpretiert werden. Logisch "falsch" sind GENAU die folgenden 13/14
Werte (die Konstanten "falsch" und "undefiniert", Wert 0 jedes numerischen
Datentyps, leere Sequenzen und Collections):

  +---------------+-------------------------------------------------------+
  | Wert          | Beschreibung                                          |
  +---------------+-------------------------------------------------------+
  | None          | Undefinierter/ungültiger Wert                         |
  | False         | Logisch falscher Wert                                 |
  +---------------+-------------------------------------------------------+
  | 0             | Ganzzahl Null                                         |
  | 0.0           | Fließkommazahl Null                                   |
  | 0l  0L        | Ganzzahl Null (Typ "long", nur Python 2)              |
  | 0+0j  0+0J    | Komplexe Zahl Null (Realteil + Imaginärteil)          |
  | Decimal(0)    | Dezimalzahl 0 (interessant für Finanzbuchhaltung)     |
  | Fraction(0,1) | Bruch 0/1                                             |
  +---------------+-------------------------------------------------------+
  | ""            | Leere Zeichenkette (auch ''  r""  r''  """""" ...)    |
  | ()            | Leerer Tupel                                          |
  | []            | Leere Liste                                           |
  | {}            | Leeres Dictionary                                     |
  | set()         | Leere Menge                                           |
  | range(0)      | Leerer Wertebereich                                   |
  +---------------+-------------------------------------------------------+

ALLE ANDEREN Werte werden als logisch True interpretiert, z.B. folgende
(Auswahl):

  +----------------+-------------------------------------------+
  | Wert           | Bedeutung                                 |
  +----------------+-------------------------------------------+
  | True           | Logisch wahrer Wert                       |
  | 1              | Zahl 1                                    |
  | -1             | Negative Zahl -1                          |
  | 0.1            | Fließkommazahl 0.1                        |
  | Decimal("0.1") | Dezimalzahl 0.1                           |
  | Fraction(1,2)  | Bruch 1/2                                 |
  +----------------+-------------------------------------------+
  | " "            | Zeichenkette aus 1 Leerzeichen            |
  | "     "        | Zeichenkette aus 5 Leerzeichen            |
  | "\n"           | Zeichenkette aus 1 Newline                |
  | "\r"           | Zeichenkette aus 1 Carriage Return        |
  | "\t"           | Zeichenkette aus 1 Tabulator              |
  | "None"         | Zeichenkette aus Text "None"              |
  | "False"        | Zeichenkette aus Text "False"             |
  | "0"            | Zeichenkette aus 1 Zeichen "0"            |
  | "00"           | Zeichenkette aus 2 Zeichen "00"           |
  | "+0"           | Zeichenkette aus 2 Zeichen "+0"           |
  | "-0"           | Zeichenkette aus 2 Zeichen "-0"           |
  | "0.0"          | Zeichenkette aus 3 Zeichen "0.0"          |
  | "abc"          | Beliebige Zeichenkette ab 1 Zeichen Länge |
  +----------------+-------------------------------------------+
  | (0,)           | Tupel mit mind. 1 Wert                    |
  | [0]            | Liste mit mind. 1 Wert                    |
  | {0:0}          | Dictionary mit mind. 1 Wert               |
  | {0}            | Set mit mind. 1 Wert                      |
  | range(1)       | Set mit mind. 1 Wert                      |
  +----------------+-------------------------------------------+

HINWEIS: In JEDER Programmiersprache gibt es eine EXAKTE DEFINITION, welche
Werte als logisch "Falsch" und welche als logisch "Wahr" interpretiert werden.
Üblich ist, dass JEDER Wert IMPLIZIT als Boolescher Wert interpretiert wird.
EXPLIZIT erfolgt eine Konvertierung in einen Booleschen Wert durch:

  !!WERT         # 2x-ige Negation (C, C++, Java, ...)
  not not WERT   # 2x-ige Negation (Python, SQL, ...)
  bool(WERT)     # Konvertierung in Python

1b) Boolescher Kontext in if/while   (Toc)

Die Anweisungen "if" und "while" erzwingen einen "Booleschen Kontext", in dem
der Wert der steuernden Ausdrucks EXPR (Expression/Bedingung) als Boolescher
Wert interpretiert wird:

  if EXPR: ...       # Entspricht if bool(EXPR): ...
  while EXPR: ...    # Entspricht while bool(EXPR): ...

1c) Zusammenhang mit Zahlen-Datentypen int/float/complex   (Toc)

Der Datentyp "bool" ist eine Spezialisierung des Datentyps "int", d.h. die
Booleschen Werte "True/False" werden in Rechenausdrücken als 1/0 interpretiert.
In Listen/Tupeln werden sie als Index 1/0 interpretiert. Ebenso werden sie in
Dictionaries/Sets mit den Schlüsseln 1/0 (int), 1.0/0.0 (float) und 1+0j/0+0j
(complex) identifiziert.

2) Logische Operatoren   (Toc)

Die logischen Operatoren and, or und not liefern angewendet auf Wahrheitswerte
(oder beliebige andere Werte, die dann gemäß obiger Tabelle als Wahrheitswerte
interpretiert werden) folgende Resultate:

  +------------+---------------------------------------------------------+
  | Operator   | Bedeutung                                               |
  +------------+---------------------------------------------------------+
  | A and B    | UND: Resultat True wenn beide Operanden A und B True    |
  |            |      Resultat False sonst                               |
  | A or B     | ODER: Resultat False wenn beide Operanden A und B False |
  |            |       Resultat True sonst                               |
  | not A      | NICHT: Resultat True wenn Operand A False               |
  |            |        Resultat False wenn Operand A True               |
  +------------+---------------------------------------------------------+

Bei Kombination von and, or und not in einem Ausdruck gilt folgender Vorrang
(wie in allen anderen Programmiersprachen auch):

  +-----+-------------+
  | not | höchster    |
  | and | mittlerer   |
  | or  | niedrigster |
  +-----+-------------+

Der Wert None und die Booleschen Werte True und False werden bei der Ausgabe
per print(...) als Text "None, "True" und "False" dargestellt.

  print(None)    # --> "None"
  print(True)    # --> "True"
  print(False)   # --> "False"

Die Booleschen Werte True und False werden bei formatierter Ausgabe als 1 und 0
dargestellt.

  print("%d" % True)            # --> 1
  print("%d" % False)           # --> 0
  print("%d" % None)            # --> TypeError: %d format: a number is required, not NoneType
  print("{:d}".format(True))    # --> 1
  print("{:d}".format(False))   # --> 0
  print("{:d}".format(None))    # --> TypeError: unsupported format string passed to NoneType.__format__
  print(F"{True:d}")            # --> 1
  print(F"{False:d}")           # --> 0
  print(F"{None:d}")            # --> TypeError: unsupported format string passed to NoneType.__format__
  print("{:s}".format(True))    # --> ValueError: Unknown format code 's'
  print("{:s}".format(False))   # -->             for object of type 'bool'
  print("{:s}".format(None))    # --> TypeError: unsupported format string passed to NoneType.__format__

Der logische Operator not liefert IMMER True oder False zurück:

  not True    # --> False
  not False   # --> True
  not "abc"   # --> False
  not ""      # --> True
  not 0       # --> True
  not 1       # --> False
  not []      # --> True

Die logischen Operatoren and und or liefern IMMER den LETZTEN für das
Ergebnis ausgewerteten Wert (interpretierbar als Wahrheitswert) zurück:

  5 or "abc"   # --> 5     = True
  0 or "abc"   # --> "abc" = True
  0 or ""      # --> ""    = False (leerer String)
  1 and 234    # --> 234   = True
  9 and None   # --> None  = False
  0 and []     # --> 0     = False

HINWEIS: Die aus C/C++/... bekannten Syntaxformen && || ! für die Booleschen
Operatoren gibt es in Python nicht! Die Operatoren & | ~ ^ << >> für die
bitweisen Operationen and or not xor shift left/right gibt es hingegen schon!

3) Ausdrücke mit logischen Werten   (Toc)

In Ausdrücken zählt True als Zahl 1 und False als Zahl 0.
D.h. Berechnungen mit Booleschen Werten sind erlaubt:

  1 - True               # --> 0
  False + 1              # --> 1
  True * True            # --> 1
  True * False           # --> 0
  5 * True - 3 * False   # --> 5
  tage = monat_laenge(mm) + schaltjahr(jahr)     # schaltjahr --> True/False

4) Short Cut/Circuit Evaluation   (Toc)

Die logische Operatoren and und or werden VON LINKS NACH RECHTS ausgewertet
(unter Berücksichtigung ihres Vorrangs). Ergibt sich dabei ein
Zwischenergebnis, bei dem auch das Endergebnis des gesamten logischen Ausdrucks
feststeht, wird die Auswertung an dieser Stelle abgebrochen und der Rest des
logischen Ausdrucks nicht mehr ausgewertet ("Short Cut Evaluation", "Short
Circuit Evaluation", verkürzte Auswertung). Dieses Verhalten ist KEINE
Besonderheit von Python, sondern ist in vielen Programmiersprachen (z.B. C,
C++, Java, JavaScript, Perl, PHP, Ruby, Awk) genauso üblich.

* Folge von UND-Verknüpfungen (and) bricht mit Gesamtergebnis False ab,
  sobald 1x False als Zwischenergebnis vorkommt.
* Folge von ODER-Verknüpfungen (or) bricht mit Gesamtergebnis True ab,
  sobald 1x True als Zwischenergebnis vorkommt.

Beispiel:

  0 and "KEINE Ausgabe"   # --> 0         = False
  1 and "Ausgabe"         # --> "Ausgabe" = True
  1 or  "KEINE Ausgabe"   # --> 1         = True
  0 or  "Ausgabe"         # --> "Ausgabe" = True
  5 or "abc" or 7.5       # --> 5         = True
  0 or "abc" or 7.5       # --> "abc"     = True
  0 or "" or 7.5          # --> 7.5       = True
  1 and "" and 3.14       # --> ""        = False (leerer String)
  1 and 234 and 74        # --> 74        = True
  9 and None and 8        # --> None      = False
  0 or [] or {}           # --> {}        = False (leeres dict)

5) Undefinierter Wert None versus definierte Werte   (Toc)

Ein ungültiger Wert kann in Python durch None dargestellt werden (hat den Typ
"NoneType" bzw. ab PY3.7 type(None)). Variablen mit diesem Wert sind
UNDEFINIERT (analog SQL-Wert "NULL"). ALLE anderen Werte sind DEFINIERT.

Der Typ "NoneType" und sein einziger Wert None können eigentlich miteinander
identifiziert werden (Singleton). Ab PY3.7 gibt es daher den Bezeichner
"NoneType" nicht mehr, er wird ersatzweise durch type(None) dargestellt:

  NoneType = type(None)   # ab PY3.7

Folgende Tests prüfen, ob der Wert einer Variablen "var" DEFINIERT bzw.
UNDEFINIERT ist:

  if type(var) == NoneType: ...        # var UNDEFINIERT?
  if type(var) == type(None): ...      # var UNDEFINIERT? (ab PY3.7)
  if isinstance(var, NoneType): ...    # var UNDEFINIERT?
  if isinstance(var, type(None): ...   # var UNDEFINIERT? (ab PY3.7)
  if var is None: ...                  # var UNDEFINIERT?
  if var == None: ...                  # var UNDEFINIERT?

Berechnungen mit None generieren IMMER einen "TypeError":

  None + 3   # --> TypeError!
  None * 3   # --> TypeError!
  None / 3   # --> TypeError!
  None - 3   # --> TypeError!

Vergleiche mit None als Wert sind nur für Gleichheit/Ungleichheit möglich,
für den Vergleich "==" mit None liefert nur None den Wahrheitswert True.

  None == None   # --> True
  None == True   # --> False
  None != None   # --> False
  None != 123    # --> True
  None < ""      # --> TypeError!
  None > 3.14    # --> TypeError!
  None <= []     # --> TypeError!
  None >= 3+3j   # --> TypeError!

6) Test auf True oder False   (Toc)

Folgende Tests prüfen, ob ein Variablen-Wert True bzw. False ist (nur die
beiden ersten Tests sind wirklich allgemein gültig, die anderen Tests prüfen
nur EINEN Wert, aber nicht alle Werte gemäß Tabelle --> 1) Boolesche Werte.

  if VAR:                # VAR True? (GUT)
  if not VAR:            # VAR False? (GUT)
  if VAR == True:        # --> False außer VAR hat Wert True/1/1.0 (SCHLECHT)
  if VAR == False:       # --> False außer VAR hat Wert False/0/0.0 (SCHLECHT)
  if VAR != True:        # --> True außer VAR hat Wert True (SCHLECHT)
  if VAR != False:       # --> True außer VAR hat Wert False (SCHLECHT)
  if VAR is True:        # --> False außer VAR hat Wert True (SCHLECHT)
  if VAR is False:       # --> False außer VAR hat Wert False (SCHLECHT)
  if VAR is not True:    # --> True außer VAR hat Wert True (SCHLECHT)
  if VAR is not False:   # --> True außer VAR hat Wert False (SCHLECHT)

6.1) True == 1/1.0 und False == 0/0.0   (Toc)

Aus historischen Gründen (der Datentyp "bool" wurde erst später als Subtyp
von "int" hinzugefügt), liefern folgende Vergleiche alle den Wert True
(obwohl die Datentypen der verglichenen Werte unterschiedlich sind):

  True  == 1     # --> True
  True  == 1.0   # --> True
  True  == 1+0j  # --> True
  1     == 1.0   # --> True
  1     == 1+0j  # --> True
  1.0   == 1+0j  # --> True
  False == 0     # --> True
  False == 0.0   # --> True
  False == 0j    # --> True
  0     == 0.0   # --> True
  0     == 0j    # --> True
  0.0   == 0j    # --> True

HINWEIS: Nur jeweils einer der folgenden Schlüssel kann in einem Dictionary
vorhanden sein (die Zahlen 0 und 0.0 und 0j sowie 1 und 1.0 und 1+0j werden
ebenfalls als gleich betrachtet):

  True    1   1.0   1+0j   1.0+0.0j
  False   0   0.0   0+0j   0.0+0.0j

In Rechenausdrücken verhalten sich True und False wie die Werte 1 und 0:

  5 * True       # --> 5
  4 + False      # --> 4

7) Test auf leeren/gefüllten Container   (Toc)

Da JEDES Objekt im Booleschen Kontext einen Wahrheitswert ergibt, kann ein Test
auf leeren/gefüllten Container (str, tuple, list, dict, set, ...) folgendermaßen
durchgeführt werden:

  if CONT:            # CONTAINER enthält mind. 1 Element  --> True/False
  if not CONT:        # CONTAINER leer                     --> True/False
  if len(CONT) != 0:  # CONTAINER enthält mind. 1 Element  --> True/False
  if len(CONT) == 0:  # CONTAINER leer                     --> True/False
  if TUPEL != ():     # Tupel enthält mind. 1 Element      --> True/False
  if TUPEL == ():     # Tupel leer                         --> True/False
  if LISTE != []:     # Liste enthält mind. 1 Element      --> True/False
  if LISTE == []:     # Liste leer                         --> True/False
  if DICT != {}:      # Dictionary enthält mind. 1 Eintrag --> True/False
  if DICT == {}:      # Dictionary leer                    --> True/False
  if STR != "":       # String enthält mind. 1 Zeichen     --> True/False
  if STR == "":       # String leer                        --> True/False

HINWEIS: Die beiden ersten Varianten sind vom Standpunkt der Performance und
des Speicherverbrauchs aus die Geschicktesten!

8) 3-wertige Logik (analog SQL)   (Toc)

Die 3 Werte True, False und None bilden bgzl. der Operatoren and, or und not
eine 3-wertige Logik (analog SQL TRUE, FALSE, NULL), wobei None bei der
Auswertung als False zählt und daher als linker Wert von and und als rechter
Wert von or erhalten bleibt bzw. bei not zu True wird (* = Abweichung).
(and, or verhalten sich NICHT kommutativ = un-symmetrisch).
* and: True links ergibt rechten Wert, False oder None links ergibt linken Wert
* or:  True links ergibt True, False oder None links ergibt rechten Wert
* not: True ergibt False, False oder None ergibt True

  +---+---+---+---+---+
  | a | b |and| or|not|
  +---+---+---+---+---+
  | T | T | T | T | F |
  | T | F | F | T | F |
  | T | N | N | T | F |
  +---+---+---+---+---+
  | F | T | F | T | T |
  | F | F | F | F | T |
  | F | N | F | N | T |
  +---+---+---+---+---+
  | N | T | N | T | T*|
  | N | F | N*| F*| T*|
  | N | N | N | N | T*|
  +---+---+---+---+---+
  | a | b |and| or|not|
  +---+---+---+---+---+

Die 3-wertige SQL-Logik mit TRUE, FALSE, NULL verhält sich etwas anders
(NULL zählt nicht als FALSE, sondern als "undefiniert" und ist somit Ergebnis,
falls kein anderweitiges eindeutiges Ergebnis möglich ist (* = Abweichung)
(and, or, xor verhalten sich kommutativ = symmetrisch).

* AND: TRUE links ergibt rechten Wert, FALSE links oder rechts ergibt FALSE, sonst NULL
* OR:  FALSE links ergibt rechten Wert, TRUE links oder rechts ergibt TRUE, sonst NULL
* XOR: Ein NULL ergibt NULL, zwei TRUE oder zwei FALSE ergeben FALSE, sonst TRUE
* NOT: NULL ergibt NULL, TRUE ergibt FALSE, FALSE ergibt TRUE

  +---+---+---+---+---+---+
  | a | b |and|or |not|xor|
  +---+---+---+---+---+---+
  | T | T | T | T | F | F |
  | T | F | F | T | F | T |
  | T | N | N | T | F | N |
  +---+---+---+---+---+---+
  | F | T | F | T | T | T |
  | F | F | F | F | T | F |
  | F | N | F | N | T | N |
  +---+---+---+---+---+---+
  | N | T | N | T | N*| N |
  | N | F | F*| N*| N*| N |
  | N | N | N | N | N*| N |
  +---+---+---+---+---+---+
  | a | b |and|or |not|xor|
  +---+---+---+---+---+---+