Kapitel 4. Verstehen von Datentypen in Python
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Effektive datengesteuerte Wissenschaft und Berechnungen erfordern ein Verständnis dafür, wie Daten gespeichert und bearbeitet werden. Dieses Kapitel beschreibt und vergleicht, wie Arrays in der Sprache Python selbst gehandhabt werden und wie NumPy dies verbessert. Das Verständnis dieses Unterschieds ist grundlegend für das Verständnis des restlichen Buches.
Die Benutzer von Python werden oft durch die Benutzerfreundlichkeit angelockt, zu der auch die dynamische Typisierung gehört. Während in einer statisch typisierten Sprache wie C oder Java jede Variable explizit deklariert werden muss, entfällt diese Angabe in einer dynamisch typisierten Sprache wie Python. In C könntest du zum Beispiel eine bestimmte Operation wie folgt angeben:
/* C code */
int
result
=
0
;
for
(
int
i
=
0
;
i
<
100
;
i
++
){
result
+=
i
;
}
In Python könnte die entsprechende Operation folgendermaßen geschrieben werden:
# Python code
result
=
0
for
i
in
range
(
100
):
result
+=
i
Beachte einen wesentlichen Unterschied: In C werden die Datentypen jeder Variablen explizit deklariert, während in Python die Typen dynamisch abgeleitet werden. Das bedeutet zum Beispiel, dass wir jeder Variablen jede Art von Daten zuweisen können:
# Python code
x
=
4
x
=
"four"
Hier haben wir den Inhalt von x
von einer Ganzzahl in einen String umgewandelt. Das Gleiche würde in C (je nach Compiler-Einstellungen) zu einem Kompilierungsfehler oder anderen unbeabsichtigten Folgen führen:
/* C code */
int
x
=
4
;
x
=
"four"
;
// FAILS
Diese Art von Flexibilität ist ein Element, das Python und andere dynamisch typisierte Sprachen bequem und einfach zu benutzen macht. Zu verstehen,wie das funktioniert, ist wichtig, um zu lernen, wie man Daten mit Python effizient und effektiv analysiert. Diese Typflexibilität weist aber auch darauf hin, dass Python-Variablen mehr sind als nur ihre Werte; sie enthalten auch zusätzliche Informationen über den Typ des Wertes. Wir werden das in den folgenden Abschnitten genauer untersuchen.
Eine Python-Ganzzahl ist mehr als nur eine Ganzzahl
Die Standardimplementierung von Python ist in C geschrieben. Das bedeutet, dass jedes Python-Objekt einfach eine geschickt getarnte C-Struktur ist, die nicht nur ihren Wert, sondern auch andere Informationen enthält. Wenn wir zum Beispiel eine ganze Zahl in Python definieren, wie x = 10000
, ist x
nicht nur eine "rohe" ganze Zahl. Es handelt sich vielmehr um einen Zeiger auf eine zusammengesetzte C-Struktur, die mehrere Werte enthält. Wenn wir uns den Quellcode von Python 3.10 ansehen, sehen wir, dass die Definition des Typs Integer (Long) in etwa so aussieht (nachdem die C-Makros erweitert wurden):
struct
_longobject
{
long
ob_refcnt
;
PyTypeObject
*
ob_type
;
size_t
ob_size
;
long
ob_digit
[
1
];
};
Eine einzelne Ganzzahl in Python 3.10 enthält eigentlich vier Teile:
-
ob_refcnt
eine Referenzzählung, die Python hilft, Speicherzuweisungen und -freigaben geräuschlos zu handhaben -
ob_type
die den Typ der Variablen kodiert -
ob_size
, die die Größe der folgenden Datenelemente angibt -
ob_digit
, die den tatsächlichen ganzzahligen Wert enthält, den die Python-Variable darstellen soll
Das bedeutet, dass das Speichern einer ganzen Zahl in Python im Vergleich zu einer kompilierten Sprache wie C mit einem gewissen Overhead verbunden ist, wie in Abbildung 4-1 dargestellt.
Hier ist PyObject_HEAD
der Teil der Struktur, der die Anzahl der Verweise, den Typcode und andere bereits erwähnte Elemente enthält.
Beachte den Unterschied: Eine C-Ganzzahl ist im Wesentlichen eine Bezeichnung für eine Position im Speicher, deren Bytes einen Ganzzahlwert kodieren. Eine Python-Ganzzahl ist ein Zeiger auf eine Position im Speicher, die alle Python-Objektinformationen enthält, einschließlich der Bytes, die den Ganzzahlwert enthalten. Diese zusätzlichen Informationen in der Python-Ganzzahlstruktur ermöglichen es, Python so frei und dynamisch zu kodieren. All diese zusätzlichen Informationen in den Python-Typen haben jedoch ihren Preis, was besonders in Strukturen deutlich wird, die viele dieser Objekte kombinieren.
Eine Python-Liste ist mehr als nur eine Liste
Betrachten wir nun, was passiert, wenn wir eine Datenstruktur in Python verwenden, die viele Python-Objekte enthält. Der Standardcontainer in Python, der mehrere Elemente enthält, ist die Liste. Wir können eine Liste mit ganzen Zahlen wie folgt erstellen:
In
[
1
]:
L
=
list
(
range
(
10
))
L
Out
[
1
]:
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
]
In
[
2
]:
type
(
L
[
0
])
Out
[
2
]:
int
Oder, ähnlich, eine Liste von Strings:
In
[
3
]:
L2
=
[
str
(
c
)
for
c
in
L
]
L2
Out
[
3
]:
[
'0'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
]
In
[
4
]:
type
(
L2
[
0
])
Out
[
4
]:
str
Dank der dynamischen Typisierung in Python können wir sogar heterogene Listen erstellen:
In
[
5
]:
L3
=
[
True
,
"2"
,
3.0
,
4
]
[
type
(
item
)
for
item
in
L3
]
Out
[
5
]:
[
bool
,
str
,
float
,
int
]
Diese Flexibilität hat jedoch ihren Preis: Um diese flexiblen Typen zu ermöglichen, muss jedes Element in der Liste seinen eigenen Typ, seine eigene Referenzanzahl und andere Informationen enthalten. Das heißt, jeder Eintrag ist ein vollständiges Python-Objekt. In dem speziellen Fall, dass alle Variablen denselben Typ haben, sind viele dieser Informationen überflüssig, so dass es viel effizienter sein kann, die Daten in einem Array vom festen Typ zu speichern. Der Unterschied zwischen einer Liste vom dynamischen Typ und einem Array vom festen Typ (im Stil von NumPy) ist in Abbildung 4-2 dargestellt.
Auf der Implementierungsebene enthält das Array im Wesentlichen einen einzelnen Zeiger auf einen zusammenhängenden Datenblock. Die Python-Liste hingegen enthält einen Zeiger auf einen Block von Zeigern, von denen jeder wiederum auf ein vollständiges Python-Objekt verweist, wie die Python-Ganzzahl, die wir vorhin gesehen haben. Auch hier liegt der Vorteil der Liste in der Flexibilität: Da jedes Listenelement eine vollständige Struktur ist, die sowohl Daten als auch Typinformationen enthält, kann die Liste mit Daten jedes gewünschten Typs gefüllt werden. Bei Arrays vom Typ NumPy fehlt diese Flexibilität, aber sie sind viel effizienter für die Speicherung und Bearbeitung von Daten.
Arrays festen Typs in Python
Python bietet verschiedene Möglichkeiten, Daten in effizienten Datenpuffern festen Typs zu speichern. Mit dem eingebauten Modul array
(verfügbar seit Python 3.3) kannst du dichte Arrays eines einheitlichen Typs erstellen:
In
[
6
]:
import
array
L
=
list
(
range
(
10
))
A
=
array
.
array
(
'i'
,
L
)
A
Out
[
6
]:
array
(
'i'
,
[
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
])
Hier ist 'i'
ein Typcode, der angibt, dass der Inhalt eine ganze Zahl ist.
Viel nützlicher ist jedoch das ndarray
Objekt des NumPy Pakets. Während das array
Objekt von Python eine effiziente Speicherung von Array-basierten Daten ermöglicht, bietet NumPy zusätzlich effiziente Operationen mit diesen Daten. Wir werden diese Operationen in späteren Kapiteln erkunden; als Nächstes zeige ich dir ein paar verschiedene Möglichkeiten, ein NumPy-Array zu erstellen.
Arrays aus Python-Listen erstellen
Wir beginnen mit dem Standard-Import von NumPy unter dem Aliasnp
:
In
[
7
]:
import
numpy
as
np
Jetzt können wir np.array
verwenden, um Arrays aus Python-Listen zu erstellen:
In
[
8
]:
# Integer array
np
.
array
([
1
,
4
,
2
,
5
,
3
])
Out
[
8
]:
array
([
1
,
4
,
2
,
5
,
3
])
Erinnere dich daran, dass NumPy-Arrays, anders als Python-Listen, nur Daten desselben Typs enthalten können. Wenn die Typen nicht übereinstimmen, werden sie von NumPy gemäß den Regeln für die Typumwandlung umgewandelt; hier werden Ganzzahlen in Fließkommazahlen umgewandelt:
In
[
9
]:
np
.
array
([
3.14
,
4
,
2
,
3
])
Out
[
9
]:
array
([
3.14
,
4.
,
2.
,
3.
])
Wenn wir den Datentyp des resultierenden Arrays explizit festlegen wollen, können wir das Schlüsselwort dtype
verwenden:
In
[
10
]:
np
.
array
([
1
,
2
,
3
,
4
],
dtype
=
np
.
float32
)
Out
[
10
]:
array
([
1.
,
2.
,
3.
,
4.
],
dtype
=
float32
)
Im Gegensatz zu Python-Listen, die immer eindimensionale Sequenzen sind, können NumPy-Arrays mehrdimensional sein. Hier ist eine Möglichkeit, ein mehrdimensionales Array mit einer Liste von Listen zu initialisieren:
In
[
11
]:
# Nested lists result in multidimensional arrays
np
.
array
([
range
(
i
,
i
+
3
)
for
i
in
[
2
,
4
,
6
]])
Out
[
11
]:
array
([[
2
,
3
,
4
],
[
4
,
5
,
6
],
[
6
,
7
,
8
]])
Die inneren Listen werden als Zeilen des resultierenden zweidimensionalen Arrays behandelt.
Arrays von Grund auf neu erstellen
Vor allem bei größeren Arrays ist es effizienter, Arrays mit den in NumPy integrierten Routinen von Grund auf neu zu erstellen. Hier sind einige Beispiele:
In
[
12
]:
# Create a length-10 integer array filled with 0s
np
.
zeros
(
10
,
dtype
=
int
)
Out
[
12
]:
array
([
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
])
In
[
13
]:
# Create a 3x5 floating-point array filled with 1s
np
.
ones
((
3
,
5
),
dtype
=
float
)
Out
[
13
]:
array
([[
1.
,
1.
,
1.
,
1.
,
1.
],
[
1.
,
1.
,
1.
,
1.
,
1.
],
[
1.
,
1.
,
1.
,
1.
,
1.
]])
In
[
14
]:
# Create a 3x5 array filled with 3.14
np
.
full
((
3
,
5
),
3.14
)
Out
[
14
]:
array
([[
3.14
,
3.14
,
3.14
,
3.14
,
3.14
],
[
3.14
,
3.14
,
3.14
,
3.14
,
3.14
],
[
3.14
,
3.14
,
3.14
,
3.14
,
3.14
]])
In
[
15
]:
# Create an array filled with a linear sequence
# starting at 0, ending at 20, stepping by 2
# (this is similar to the built-in range function)
np
.
arange
(
0
,
20
,
2
)
Out
[
15
]:
array
([
0
,
2
,
4
,
6
,
8
,
10
,
12
,
14
,
16
,
18
])
In
[
16
]:
# Create an array of five values evenly spaced between 0 and 1
np
.
linspace
(
0
,
1
,
5
)
Out
[
16
]:
array
([
0.
,
0.25
,
0.5
,
0.75
,
1.
])
In
[
17
]:
# Create a 3x3 array of uniformly distributed
# pseudorandom values between 0 and 1
np
.
random
.
random
((
3
,
3
))
Out
[
17
]:
array
([[
0.09610171
,
0.88193001
,
0.70548015
],
[
0.35885395
,
0.91670468
,
0.8721031
],
[
0.73237865
,
0.09708562
,
0.52506779
]])
In
[
18
]:
# Create a 3x3 array of normally distributed pseudorandom
# values with mean 0 and standard deviation 1
np
.
random
.
normal
(
0
,
1
,
(
3
,
3
))
Out
[
18
]:
array
([[
-
0.46652655
,
-
0.59158776
,
-
1.05392451
],
[
-
1.72634268
,
0.03194069
,
-
0.51048869
],
[
1.41240208
,
1.77734462
,
-
0.43820037
]])
In
[
19
]:
# Create a 3x3 array of pseudorandom integers in the interval [0, 10)
np
.
random
.
randint
(
0
,
10
,
(
3
,
3
))
Out
[
19
]:
array
([[
4
,
3
,
8
],
[
6
,
5
,
0
],
[
1
,
1
,
4
]])
In
[
20
]:
# Create a 3x3 identity matrix
np
.
eye
(
3
)
Out
[
20
]:
array
([[
1.
,
0.
,
0.
],
[
0.
,
1.
,
0.
],
[
0.
,
0.
,
1.
]])
In
[
21
]:
# Create an uninitialized array of three integers; the values will be
# whatever happens to already exist at that memory location
np
.
empty
(
3
)
Out
[
21
]:
array
([
1.
,
1.
,
1.
])
NumPy Standard Datentypen
NumPy-Arrays enthalten Werte eines einzigen Typs, daher ist es wichtig, diese Typen und ihre Grenzen genau zu kennen. Da NumPy in C entwickelt wurde, sind die Typen den Benutzern von C, Fortran und anderen verwandten Sprachen vertraut.
Die Standard-NumPy-Datentypen sind in Tabelle 4-1 aufgeführt. Beachte, dass sie beim Aufbau eines Arrays mit einer Zeichenkette angegeben werden können:
np
.
zeros
(
10
,
dtype
=
'int16'
)
Oder du verwendest das zugehörige NumPy-Objekt:
np
.
zeros
(
10
,
dtype
=
np
.
int16
)
Es ist möglich, weitere Typen zu spezifizieren, z. B. Big- oder Little-Endian-Zahlen; weitere Informationen findest du in derNumPy-Dokumentation. NumPy unterstützt auch zusammengesetzte Datentypen, die inKapitel 12 behandelt werden.
Datentyp | Beschreibung |
---|---|
|
Boolescher Wert (Wahr oder Falsch), gespeichert als Byte |
|
Standard-Integer-Typ (wie C |
|
Identisch mit C |
|
Ganzzahl, die für die Indexierung verwendet wird (wie in C |
|
Byte (-128 bis 127) |
|
Ganzzahl (-32768 bis 32767) |
|
Integer (-2147483648 bis 2147483647) |
|
Integer (-9223372036854775808 bis 9223372036854775807) |
|
Ganzzahl ohne Vorzeichen (0 bis 255) |
|
Ganzzahl ohne Vorzeichen (0 bis 65535) |
|
Ganzzahl ohne Vorzeichen (0 bis 4294967295) |
|
Ganzzahl ohne Vorzeichen (0 bis 18446744073709551615) |
|
Kurzform für |
|
Halbgenaue Fließkommazahl: Vorzeichenbit, 5 Bit Exponent, 10 Bit Mantisse |
|
Gleitkommazahl mit einfacher Genauigkeit: Vorzeichenbit, 8 Bit Exponent, 23 Bit Mantisse |
|
Doppelt genaue Fließkommazahl: Vorzeichenbit, 11 Bit Exponent, 52 Bit Mantisse |
|
Kurzform für |
|
Komplexe Zahl, dargestellt durch zwei 32-Bit-Gleitkommazahlen |
|
Komplexe Zahl, dargestellt durch zwei 64-Bit-Fließkommazahlen |
Get Python Data Science Handbook, 2. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.