Die Funktionen „any“ und „all“ in Python

Wie kann man in Python prüfen, ob alle Elemente einer Bedingung entsprechen? Und wie kann man prüfen, ob ein beliebiges Element einer Bedingung entspricht?

Man könnte denken, dass man so etwas tun könnte:

all_good = True
for item in iterable:
  if not condition(item):
      all_good = False
      break

Oder etwas wie dieses:

any_bad = False
for item in iterable:
  if condition(item):
      any_bad = True
      break

Aber es gibt zwei built-in Funktionen in Python, die helfen können, diese Codeblöcke anschaulicher und lesbarer zu machen. Schauen wir uns die Verwendung der Python-Funktionen any und all an, um zu prüfen, ob alle iterierbaren Elemente eine Bedingung erfüllen.

Prüfung einer Bedingung für alle Elemente

Die Funktion akzeptiert eine Liste (oder Iterable) von Zahlen und prüft, ob alle Zahlen zwischen 0 und 5 (einschließlich) liegen:

def ratings_valid(ratings):
  for rating in ratings:
      if rating < 0 or rating > 5:
          return False

    return True

Dieser Code:

  1. Durchläuft alle angegebenen Zahlen in einer for-Schleife
  2. Gibt False zurück, sobald eine negative Zahl oder eine Zahl größer als 5 gefunden wird
  3. Gibt True zurück, wenn alle Zahlen zwischen 0 und 5 liegen.

Hier ist zu beachten, dass diese Funktion zurückkehrt, sobald sie eine ungültige Zahl findet. Sie durchläuft also nur dann den gesamten Zahlenbereich, wenn alle Zahlen gültig sind.

Verwendung der Funktionen „any“ und „all“

Schauen wir uns zunächst eine Umstrukturierung unserer Schleife an, die für jedes Element eine Bedingung prüft, und besprechen dann, was wir getan haben, warum wir es getan haben und wie wir es getan haben.

Spoiler-Alarm! Wir werden uns den Code ansehen, den wir am Ende haben werden, und dann werden wir einen Schritt zurückgehen und unseren Weg zu diesem Code umgestalten.

Wir haben angefangen mit diesem:

def ratings_valid(ratings):
  for rating in ratings:
      if rating < 0 or rating > 5:
          return False

    return True

Und wir erhalten am Ende dieses:

def ratings_valid(ratings):
  return not any(
      rating < 0 or rating > 5
      for rating in ratings
  )

Oder mit dieser Möglichkeit, welche sogar lesbarer ist:

def ratings_valid(ratings):
  return all(
      0 <= rating <= 5
      for rating in ratings
  )

Nun, schlüsseln wir diesen Code auf, um zu sehen, wie er funktioniert. Zuerst müssen wir über die Funktionen any und all sprechen. Dann werden wir darüber sprechen, warum any und all in der Regel mit Generatorausdrücken verwendet werden und wie man zwischen ihnen wählen kann.

Pythons any-Funktion

Python hat eine built-in Funktion, die True zurückgibt, wenn irgendein Element wahr (truthy) ist:

 

>>> any([True, False, False, True, False])
True
>>> any([False, False, False, False, False])
False
>>> any(['', '', ''])
False
>>> any(['', 'Python', ''])
True

Truthy bedeutet typischerweise nicht leer oder nicht null, aber für unsere Zwecke kann man es sich ziemlich genau so vorstellen wie True.

Pythons any ist äquivalent zu diesem:

def any(iterable):
  for element in iterable:
      if element:
          return True

    return False

Hier ist die Ähnlichkeit zwischen any und der Funktion ratings_valid (s.o.) zu beachten: Ihre Struktur ist sehr ähnlich, aber nicht ganz identisch.

Die Funktion any prüft die Wahrhaftigkeit jedes Elements in einer gegebenen Iterable, aber wir brauchen etwas mehr als das: Wir müssen eine Bedingung für jedes Element prüfen. Genauer gesagt, müssen wir prüfen, ob eine Zahl zwischen 0 und 5 liegt.

Pythons all-Funktion

Python hat auch eine built-in Funktion, die True zurückgibt, wenn alle Elemente wahr sind:

>>> all([True, False, True, True, False])
False
>>> all([True, True, True, True, True])
True
>>> all(['Python', 'is', 'neat'])
True
>>> all(['', 'Python', 'is', 'neat'])
False

Pythons all ist äquivalent zu diesem:

def all(iterable):
  for element in iterable:
      if not element:
          return False

  return True

Die Funktionen any und all sind zwei Seiten der gleichen Medaille:

  • any gibt True zurück, sobald sie eine Übereinstimmung findet
  • all gibt False zurück, sobald sie eine Nicht-Übereinstimmung findet

Verwendung einer list comprehension

Die Funktionen any und all akzeptieren ein Iterable und prüfen die Wahrhaftigkeit (truthiness) jedes Elements in diesem Iterable. Diese Funktionen werden typischerweise mit Iterablen von booleschen Werten verwendet.

Wir könnten unsere Funktion ratings_valid neu implementieren, indem wir eine Liste von booleschen Werten erstellen und diese Werte dann an any übergeben:

def ratings_valid(ratings):
  rating_is_invalid = []
  for rating in ratings:
      rating_is_invalid.append(rating < 0 or rating > 5)

    return not any(rating_is_invalid)

Oder wir wandlen die for-SChleife in eine list comprehension um:

def ratings_valid(ratings):
  return not any([
      rating < 0 or rating > 5
      for rating in ratings
  ])

Dieser Code ist kürzer, aber es gibt ein Problem: Wir bauen eine neue Liste auf, nur um sie einmal zu überfliegen! Das ist weniger effizient als unser ursprünglicher Ansatz!

Unser ursprünglicher Ansatz hat nur dann eine komplette Schleife durch die Liste durchlaufen, wenn alle Bewertungen gültig waren (siehe die Rückgabe innerhalb der for-Schleife).

def ratings_valid(ratings):
  for rating in ratings:
      if rating < 0 or rating > 5:
          return False

    return True

Beheben wir diese Ineffizienz, indem wir unsere list comprehension in einen Generatorausdruck umwandeln.

Verwendung eines Generators

Ein Generator ist wie eine list comprehension, aber anstatt eine Liste zu erstellen, erzeugt er ein Generator Objekt:

def ratings_valid(ratings):
  return not any((
      rating < 0 or rating > 5
      for rating in ratings
  ))

Wir können sogar die doppelten Klammern weglassen und haben trotzdem gültigen Python-Code:

def ratings_valid(ratings):
  return not any(
      rating < 0 or rating > 5
      for rating in ratings
  )

Es ist so üblich, einen Generator-Ausdruck direkt in einen Funktionsaufruf zu übergeben, dass die Python-Kernentwickler diese Funktion nur für diesen Anwendungsfall hinzugefügt haben.

Generatoren sind „Lazy Iterables“: Sie berechnen die in ihnen enthaltenen Elemente erst, wenn Sie eine Schleife über sie durchlaufen Sie berechnen die Werte Element für Element. So können wir mit der Berechnung aufhören, sobald die Funktion any einen wahrheitsgemäßen Wert findet!

Es gibt noch eine weitere Sache, die wir tun können, um die Lesbarkeit dieses Codes zu verbessern: Wir könnten zur Verwendung von Pythons all-Funktion wechseln.

Auswählen zwischen any und all

Wir haben mit einer for-Schleife, einer if-Anweisung, einer return-Anweisung innerhalb unserer Schleife und einer return-Anweisung am Ende unserer Schleife begonnen:

def ratings_valid(ratings):
  for rating in ratings:
      if rating < 0 or rating > 5:
          return False

    return True

All das haben wir in einen Generatorausdruck verwandelt, der an die Funktion any übergeben wird:

def ratings_valid(ratings):
  return not any(
      rating < 0 or rating > 5
      for rating in ratings
  )

Es ist hier zu beachten, dass wir das Endergebnis negiert haben (siehe das not in return not any). Auf Deutsch heißt das „es gibt kein Rating außerhalb des Zielbereichs“.

Mit der Funktion all können wir diese Negation aufheben:

def ratings_valid(ratings):
  return all(
      not (rating < 0 or rating > 5)
      for rating in ratings
  )

Oder wenn wir uns auf die verketteten Vergleiche von Python verlassen, könnten wir schreiben:

def ratings_valid(ratings):
  return all(
      0 <= rating <= 5
      for rating in ratings
  )

Auf Deutsch heißt das „alle Ratings liegen innerhalb des Zielbereichs“.

Diese beiden Anweisungen sind in Python gleichwertig:

>>> all_match = all(condition(x) for x in iterable)
>>> all_match = not any(not condition(x) for x in iterable)

Und auch diese beiden Anweisungen sind gleichwertig:

>>> any_match = any(condition(x) for x in iterable)
>>> any_match = not all(not condition(x) for x in iterable)

In der Regel wird einer der any- oder all-Ausdrücke deutlicher zu lesen sein als der andere. In unserem Fall ist es der „all“-Ausdruck, der lesbarer ist.

Verwendung von any und all in einem if-Befehl

Meistens wurden die Funktionen mit einer return-Anweisung gezeigt, aber das ist eigentlich nicht die lesbarste Verwendung.
Wenn man das Verhalten dieser Funktionen nicht intuitiv findet, sind sie vielleicht leichter zu verstehen, wenn sie in einer if-Anweisung verwendet werden.

Nehmen wir zum Beispiel an, wir haben eine is_valid_rating-Funktion wie diese:

def is_valid_rating(rating):
  return 0 <= rating <= 5

Wir könnten diese Funktion verwenden, um eine if-Anweisung wie diese zu schreiben:

if all(is_valid_rating(r) for r in ratings):
  print("All the ratings are valid")
else:
  print("Not all ratings are valid")

Ich würde diesen Code so beschreiben, dass er prüft, ob „alle Ratings gültig sind“ und dann auf der Grundlage des Ergebnisses, die Aussage ausgibt.

Vergleichen wir den Code hiermit:

all_valid = True
for r in ratings:
  if not is_valid_rating(r):
      all_valid = False
      break


if all_valid:
  print("All the ratings are valid")
else:
  print("Not all ratings are valid")

In einfachen Worten heißt das „durchlaufe alle Ratings und setze all_valid auf False, wenn eine ungültige Bewertung gefunden wird, ansonsten setze True, und dann auf der Grundlage des Wertes von all_valid gebe die entsprechende Aussage aus“.

Diese for-Schleife lässt sich nicht annähernd so leicht beschreiben wie die if-Anweisung mit all! In diiesem Fall sollte man den ersten Ansatz mit der Verwendung eines Generatorausdrucks mit all vorziehen, weil er anschaulicher ist als die entsprechende for-Schleife.

Vielleicht braucht man nur einen Containment-Check

Pythons Funktionen any and all dienen dazu, eine Bedingung für jedes Element in einer Liste (oder eines anderen Iterable) zu prüfen. Obwohl any und all praktisch sind, gibt es manchmal ein noch einfacheres Werkzeug zur Überprüfung von Bedingungen.

Zum Beispiel diese Schleife:

>>> numbers = [2, 1, 3, 4, 7, 11]
>>> has_five = False
>>> for n in numbers:
... if n == 5:
... has_five = True
... break
...

Sie kann auch so geschrieben werden:

>>> numbers = [2, 1, 3, 4, 7, 11]
>>> has_five = any(n == 5 for n in numbers)

Aber dies könnte noch weiter vereinfacht werden. Wir versuchen zu prüfen, ob eine bestimmte Zahl in der Zahlenliste enthalten ist.

Der in-Operator ist genau für diesen Zweck gemacht:

>>> numbers = [2, 1, 3, 4, 7, 11]
>>> has_five = 5 in numbers

Der in-Operator von Python dient zur Überprüfung von Inhalten. Immer wenn man wissen will, ob ein bestimmtes Element in einem bestimmten Iterable enthalten ist, ist der in-Operator das richtige Werkzeug.

Prüfen mit den Funktionen any und all, ob alle Elemente eine Bedingung erfüllen

Pythons any- und all-Funktionen wurden für die Verwendung mit Generatorausdrücken entwickelt und werden selten ohne sie benutzt.

Wenn man das nächste Mal prüfen muss, ob alle Elemente einer Bedingung entsprechen, sollte man Pythons any- oder all-Funktionen zusammen mit einem Generatorausdruck ausprobieren.