Built-in Funktionen in Python – Teil 2

Die 10 häufig übersehenen Built-Ins

Wenn man schon ein wenig mit Python programmiert oder gerade einen Einführungskurs in Python besucht hat, kennt man wahrscheinlich schon die genannten built-in Funktionen aus dem Beitrag „Built-in Funktionen in Python – Teil 1„.
Jetzt schauen wir uns die 10 „eingebauten“ Funktionen an, die sehr nützlich sind, aber von Python-Neulingen häufig übersehen werden.

bool

Die Funktion bool prüft die „Wahrhaftigkeit“ (truthiness) eines Python-Objekts.
Bei Zahlen ist das Ergebnis eine Frage von „Null oder nicht Null“:

>>> bool(5)
True
>>> bool(-1)
True
>>> bool(0)
False

Bei Mengen (collections) ist die „truthiness“ in der Regel eine Frage, ob leer oder nicht (ob die Menge eine Länge größer als 0 hat):

>>> bool('hello')
True
>>> bool('')
False
>>> bool(['a'])
True
>>> bool([])
False
>>> bool({})
False
>>> bool({1: 1, 2: 4, 3: 9})
True
>>> bool(range(5))
True
>>> bool(range(0))
False
>>> bool(None)
False

Wahrhaftigkeit ist eine ziemlich große Sache in Python.

Anstatt Fragen über die Länge eines Containers zu stellen, stellen viele Pythonistas stattdessen Fragen über die „truthiness“:

# nicht empfehlenswert
if len(numbers) == 0:
    print("Die Liste ist leer")

# empfohlen 
if not numbers:
    print("Die Liste ist leer")

Man wird bool wahrscheinlich nicht oft verwenden, aber wenn man einmal einen Wert in einen boolschen Wert umwandeln muss, um seine truthiness abzufragen, ist es nützlich bool zu kennen.

enumerate

Möchte man eine Zahl hochzählen und gleichzeitig eine Schleife über ein Iterable durchführen, ist die Funktion enumerate sehr nützlich.
Das mag wie eine Nischenaufgabe erscheinen, aber sie kommt recht häufig vor.

Man möchte zum Beispiel die Zeilennummer in einer Datei verfolgen:

>>> with open('hello.txt', mode='rt') as my_file:
... for n, line in enumerate(my_file, start=1):
... print(f"{n:03}", line)
...
001 This is the first line of the file
002 This is the second line
003 This is the last line of the file

Die Funktion enumerate wird auch sehr häufig verwendet, um den Index von Elementen in einer Folge zu ermitteln.

# empfehlenswert
def palindromic(sequence):
    """Return True if the sequence is the same thing in reverse."""
    for i, item in enumerate(sequence):
        if item != sequence[-(i+1)]:
            return False
    return True

Es kommt oft vor, dass neuere Pythonistas vielleicht range(len(sequence)) in Python verwenden. Wenn man Code mit range(len(…)) sieht, ist es sehr wahrscheinlich, dass man hier stattdessen enumerate verwenden sollte.

# nicht empfehlenswert
def palindromic(sequence):
    """Return True if the sequence is the same thing in reverse."""
    for i in range(len(sequence)):
        if sequence[i] != sequence[-(i+1)]:
            return False
    return True

zip

Die Funktion zip ist noch spezieller als enumerate.
Die zip-Funktion wird verwendet, um in einer Schleife über mehrere Iterablen gleichzeitig zu laufen.

>>> one_iterable = [2, 1, 3, 4, 7, 11]
>>> another_iterable = ['P', 'y', 't', 'h', 'o', 'n']
>>> for n, letter in zip(one_iterable, another_iterable):
... print(letter, n)
...
P 2
y 1
t 3
h 4
o 7
n 11

Wenn man eine Schleife über zwei Listen (oder andere Iterable) gleichzeitig ausführen möchte, ist zip der Funktion enumerate vorzuziehen. Die Funktion enumerate ist praktisch, wenn man während der Schleife Indizes benötigt, aber zip ist großartig, wenn es darum geht, über zwei Iterablen gleichzeitig durchzulaufen.

Sowohl enumerate als auch zip geben uns Iteratoren zurück. Iteratoren sind die lazy iterables, die for-Schleifen antreiben.

reversed

Die reversed-Funktion gibt, wie enumerate und zip, einen Iterator zurück.

>>> numbers = [2, 1, 3, 4, 7]
>>> reversed(numbers)
<list_reverseiterator object at 0x7f3d4452f8d0>

Das Einzige, was wir mit diesem Iterator machen können, ist, ihn mit einer Schleife zu durchlaufen (aber nur einmal!):

>>> numbers = [2, 1, 3, 4, 7]
>>> reversed_numbers = reversed(numbers)
>>> list(reversed_numbers)
[7, 4, 3, 1, 2]
>>> list(reversed_numbers)
[]

 

Wie enumerate und zip ist auch reversed eine Art Hilfsfunktion für Schleifen. Man wird reversed so gut wie ausschließlich im for-Teil einer for-Schleife verwenden:

>>> numbers = [2, 1, 3, 4, 7]
>>> for n in reversed(numbers):
... print(n)
...
7
4
3
1
2

Neben der Funktion reversed gibt es noch einige andere Möglichkeiten, Python-Listen umzukehren:

# slicing syntax
for n in numbers[::-1]:
    print(n)
# in-place reverse method
numbers.reverse()
for n in numbers:
    print(n)

Aber die Funktion reversed ist normalerweise der beste Weg, um ein beliebiges Iterable in Python umzukehren.

Anders als die Methode zum Umkehren von Listen (z. B. numbers.reverse()) verändert reversed die Liste nicht (stattdessen wird ein Iterator der umgekehrten Elemente zurückgegeben).

Anders als die Slice-Syntax numbers[::-1] baut reversed(numbers) keine komplett neue Liste auf: Der zurückgegebene Lazy-Iterator holt sich in der Schleife das nächste Element in umgekehrter Reihenfolge. Außerdem ist reversed(numbers) viel lesbarer als numbers[::-1] (was einfach nur seltsam aussieht, wenn man diese spezielle Verwendung von Slicing noch nie gesehen hat).

Wenn wir die nicht kopierenden Funktionen reversed und zip kombinieren, können wir die palindromic-Funktion (aus enumerate oben) umschreiben, ohne zusätzlichen Speicher zu benötigen (hier wird keine Liste kopiert):

def palindromic(sequence):
    """Return True if the sequence is the same thing in reverse."""
    for n, m in zip(sequence, reversed(sequence)):
        if n != m:
            return False
    return True

sum

Die Funktion sum nimmt ein Iterable von Zahlen und gibt die Summe dieser Zahlen zurück.

>>> sum([2, 1, 3, 4, 7])
17

Python verfügt über eine Vielzahl von Hilfsfunktionen, die die Schleifenbildung übernehmen, auch weil sie sich gut mit Generatorausdrücken kombinieren lassen:

>>> numbers = [2, 1, 3, 4, 7, 11, 18]
>>> sum(n**2 for n in numbers)
524

min und max

Die Funktionen min und max tun das, was man erwarten würde: Sie geben das Minimum und das Maximum der Elemente in einem Iterable an.

>>> numbers = [2, 1, 3, 4, 7, 11, 18]
>>> min(numbers)
1

>>> max(numbers)
18

sorted

Die Funktion sorted nimmt ein beliebiges Iterable und gibt eine neue Liste aller Werte dieses Iterable in sortierter Reihenfolge zurück.

>>> numbers = [1, 8, 2, 13, 5, 3, 1]
>>> words = ["python", "is", "lovely"]
>>> sorted(words)
['is', 'lovely', 'python']
>>> sorted(numbers, reverse=True)
[13, 8, 5, 3, 2, 1, 1]

Die sorted-Funktion vergleicht wie min und max die ihr übergebenen Elemente mit Hilfe des < Operators, so dass alle ihr übergebenen Werte sortierbar sein müssen.

Die sorted-Funktion ermöglicht auch die Anpassung ihrer Sortierung über eine key-Funktion (genau wie min und max).

>>> fruits = ['kumquat', 'Cherimoya', 'Loquat', 'longan', 'jujube']
>>> sorted(fruits, key=len)
['Loquat', 'longan', 'jujube', 'kumquat', 'Cherimoya']

any und all

Die Funktionen any und all können mit einem Generatorausdruck kombiniert werden, um festzustellen, ob ein oder alle Elemente in einem Iterable einer bestimmten Bedingung entsprechen.

Die palindromic-Funktion (s. oben) prüft, ob alle Elemente gleich dem entsprechenden Element in der umgekehrten Reihenfolge sind (ist der erste Wert gleich dem letzten, der zweite gleich dem vorletzten usw.).

Man kann diese Funktion mit all folgendermaßen umschreiben:

def palindromic(sequence):
    """Return True if the sequence is the same thing in reverse."""
    return all(
        n == m
        for n, m in zip(sequence, reversed(sequence)
)

Die Negierung der Bedingung und des Rückgabewerts von all würde es uns ermöglichen, any gleichwertig zu verwenden (obwohl dies in diesem Beispiel eher verwirrend ist):

def palindromic(sequence):
    """Return True if the sequence is the same thing in reverse."""
    return not any(
        n != m
        for n, m in zip(sequence, reversed(sequence))
    )

Die 5 Built-Ins für die Fehlersuche (debugging)

Die folgenden 5 Funktionen sind für das Debuggen und die Fehlersuche im Code nützlich.

breakpoint

Wie kann man die Ausführung des Codes unterbrechen und in eine Python-Eingabeaufforderung wechseln? Man braucht breakpoint!

Wenn man die Funktion breakpoint aufruft, gelangt man zu pdb, dem Python-Debugger. Es gibt viele Tutorials und Vorträge über PDB. Ausführlich ist die Original-Anleitung (engl.), es gibt auch kurze Übersichten auf deutsch, z.B. https://www.netways.de/blog/2012/03/29/der-python-debugger/ und https://runebook.dev/de/docs/python/library/pdb

Diese eingebaute Funktion wurde in Python 3.7 hinzugefügt. In älteren Versionen von Python kann man stattdessen

import pdb 
pdb.set_trace()

verwenden.

 

dir

 

Die Funktion dir kann für zwei Dinge verwendet werden:

 

  • eine Liste aller lokalen Variablen anzeigen 
  • eine Liste aller Attribute eines bestimmten Objekts anzeigen

 

Hier sieht man lokale Variablen direkt nach dem Start einer neuen Python-Shell und nach der Erstellung einer neuen Variable x:

 

>>> dir()
['__annotations__', '__doc__', '__name__', '__package__']
>>> x = [1, 2, 3, 4] >>> dir() ['__annotations__', '__doc__', '__name__', '__package__', 'x']

 

Wenn man diese x-Liste an dir übergibt, kann man alle Attribute sehen, die sie hat:

 

>>> dir(x)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', 
'__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', 
'__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 
'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

 

Man sieht die typischen Listenmethoden, append, pop, remove, und mehr, sowie viele dunder-Methoden für Operator-Überladung.

 

vars

 

Die Funktion vars ist eine Mischung aus zwei verwandten Dingen: der Überprüfung von locals() und dem Testen des Attributs __dict__ von Objekten.

 

Wenn vars ohne Argumente aufgerufen wird, entspricht dies dem Aufruf der eingebauten Funktion locals() (die ein Wörterbuch aller lokalen Variablen und ihrer Werte anzeigt).

 

>>> vars()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>,
'__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}

 

Wenn vars mit einem Argument aufgerufen wird, greift es auf das __dict__-Attribut dieses Objekts zu (das bei vielen Objekten ein Wörterbuch mit allen Instanzattributen darstellt).

 

>>> from itertools import chain
>>> vars(chain)
mappingproxy({'__getattribute__': <slot wrapper '__getattribute__' of 'itertools.chain' objects>, '__iter__': <slot wrapper '__iter__' of 'itertools.chain' objects>,
'__next__': <slot wrapper '__next__' of 'itertools.chain' objects>, '__new__': <built-in method __new__ of type object at 0x5611ee76fac0>, 
'from_iterable': <method 'from_iterable' of 'itertools.chain' objects>, '__reduce__': <method '__reduce__' of 'itertools.chain' objects>, 
'__setstate__': <method '__setstate__' of 'itertools.chain' objects>, '__doc__': 'chain(*iterables) --> chain object\n\nReturn a chain object whose .__next__() 
method returns elements from the\nfirst iterable until it is exhausted, then elements from the next\niterable, until all of the iterables are exhausted.'})

 

Bevor man my_object.__dict__ benutzt, sollte man daran denken eher vars zu verwenden.

Genauso sollte man dir gegenüber vars bevorzugen.

 

type

 

Die Funktion type bestimmt den Typ des Objekts, das man ihr übergibt.
Der Typ einer Klasseninstanz ist die Klasse selbst:

 

>>> x = [1, 2, 3]
>>> type(x)
<class 'list'>

 

Der Typ einer Klasse ist ihre Metaklasse, die normalerweise type ist:

 

>>> type(list)
<class 'type'>
>>> type(type(x))
<class 'type'>

 

Wenn man jemals jemanden __class__ verwenden sieht, sollte manwissen, dass er stattdessen auch nach der übergeordneten Typfunktion greifen könnte:

 

>>> x.__class__
<class 'list'>
>>> type(x)
<class 'list'>

 

Die type-Funktion ist manchmal im eigentlichen Code hilfreich (insbesondere bei objektorientiertem Code mit Vererbung und benutzerdefinierten Zeichenkettendarstellungen), sie ist aber auch bei der Fehlersuche nützlich.

 

Es ist aber zu beachten, dass bei der Typüberprüfung in der Regel die Funktion isinstance anstelle von type verwendet wird!

 

help

 

Wenn man sich in einer interaktiven Python-Shell (der Python-REPL) befindet, vielleicht Code mit Hilfe von breakpoints debugged und wissen möchte, wie ein bestimmtes Objekt, eine Methode oder ein Attribut funktioniert, ist die help-Funktion sehr nützlich.

 

Realistisch betrachtet wird man wahrscheinlich häufiger auf die Hilfe seiner Lieblingssuchmaschine zurückgreifen als auf die help-Funktion. Aber wenn man sich bereits in einer Python-REPL befindet, ist es schneller, z.B. help(list.insert) aufzurufen, als die Dokumentation der Methode list.insert im Internet nachzuschlagen.