Zeiger in Python: Was ist der Sinn?


Wenn Sie jemals mit einfachen Sprachen wie C oder C ++ gearbeitet haben, haben Sie wahrscheinlich von Zeigern gehört. Mit ihnen können Sie die Effektivität verschiedener Codeteile erheblich steigern. Sie können aber auch Anfänger - und sogar erfahrene Entwickler - verwirren und zu Fehlern bei der Speicherverwaltung führen. Gibt es Zeiger in Python, kann ich sie irgendwie emulieren?

Zeiger werden häufig in C und C ++ verwendet. Tatsächlich sind dies Variablen, die die Speicheradressen enthalten, an denen sich andere Variablen befinden. Lesen Sie diese Rezension , um Zeiger aufzufrischen .

Dank dieses Artikels können Sie das Objektmodell in Python besser verstehen und herausfinden, warum Zeiger in dieser Sprache nicht vorhanden sind. Wenn Sie das Verhalten von Zeigern simulieren müssen, lernen Sie, wie Sie sie ohne den damit verbundenen Albtraum der Speicherverwaltung emulieren.

Mit diesem Artikel haben Sie:

  • Erfahren Sie, warum Python keine Zeiger hat.
  • Lernen Sie den Unterschied zwischen C-Variablen und Namen in Python.
  • Erfahren Sie, wie Sie Zeiger in Python emulieren.
  • Verwenden Sie ctypes um mit echten Zeigern ctypes experimentieren.

Hinweis : Hier wird der Begriff „Python“ auf die Python-Implementierung in C angewendet, die als CPython bezeichnet wird. Alle Diskussionen des Sprachgeräts gelten für CPython 3.7, entsprechen jedoch möglicherweise nicht den nachfolgenden Iterationen.

Warum gibt es in Python keine Zeiger?


Weiß nicht. Können Zeiger in Python nativ vorhanden sein? Wahrscheinlich, aber anscheinend widersprechen die Zeiger dem Konzept des Zen von Python , weil sie implizite Änderungen anstelle expliziter provozieren. Zeiger sind oft sehr komplex, besonders für Anfänger. Darüber hinaus drängen sie Sie zu erfolglosen Entscheidungen oder zu etwas wirklich Gefährlichem, wie dem Lesen aus einem Speicherbereich, in dem Sie es nicht hätten lesen sollen.

Python versucht, Implementierungsdetails vom Benutzer zu abstrahieren, z. B. eine Speicheradresse. In dieser Sprache liegt der Schwerpunkt häufig auf Benutzerfreundlichkeit und nicht auf Geschwindigkeit. Daher sind Zeiger in Python nicht sehr sinnvoll. Aber keine Sorge, die Sprache bietet Ihnen standardmäßig einige Vorteile der Verwendung von Zeigern.

Um die Zeiger in Python zu verstehen, gehen wir kurz auf die Funktionen der Sprachimplementierung ein. Insbesondere müssen Sie verstehen:

  1. Was sind veränderliche und unveränderliche Objekte?
  2. Wie sind Variablen / Namen in Python angeordnet?

Halten Sie an Ihren Speicheradressen fest, los geht's!

Objekte in Python


Alles in Python ist ein Objekt. Öffnen Sie beispielsweise REPL und sehen Sie, wie isinstance() :

 >>> isinstance(1, object) True >>> isinstance(list(), object) True >>> isinstance(True, object) True >>> def foo(): ... pass ... >>> isinstance(foo, object) True 

Dieser Code zeigt, dass alles in Python tatsächlich ein Objekt ist. Jedes Objekt enthält mindestens drei Datentypen:

  • Referenzzähler.
  • Typ
  • Wert.

Ein Referenzzähler wird zum Verwalten des Speichers verwendet. Details zu dieser Verwaltung finden Sie in der Speicherverwaltung in Python . Der Typ wird auf CPython-Ebene verwendet, um die Typensicherheit zur Laufzeit zu gewährleisten. Und value ist der tatsächliche Wert, der dem Objekt zugeordnet ist.

Aber nicht alle Objekte sind gleich. Es gibt einen wichtigen Unterschied: Objekte sind veränderlich und unveränderlich. Wenn Sie diese Unterscheidung zwischen Objekttypen verstehen, können Sie die erste Schicht der Zwiebel, die als "Zeiger in Python" bezeichnet wird, besser verstehen.

Veränderliche und unveränderliche Objekte


In Python gibt es zwei Arten von Objekten:

  1. Unveränderliche Objekte (können nicht geändert werden);
  2. Änderbare Objekte (Änderungen vorbehalten).

Das Erkennen dieses Unterschieds ist der erste Schlüssel, um durch die Welt der Zeiger in Python zu reisen. Hier ist eine Charakterisierung der Unveränderlichkeit einiger populärer Typen:

Typ
Unveränderlich?
int
Ja
float
Ja
Bool
Ja
komplex
Ja
Tupel
Ja
Frozenset
Ja
str
Ja
Liste
Nein
einstellen
Nein
diktieren
Nein

Wie Sie sehen können, sind viele der häufig verwendeten primitiven Typen unveränderlich. Sie können dies überprüfen, indem Sie Python-Code schreiben. Sie benötigen zwei Tools aus der Standardbibliothek:

  1. id() gibt die Speicheradresse des Objekts zurück;
  2. is gibt nur dann True wenn zwei Objekte dieselbe Speicheradresse haben.

Sie können diesen Code in einer REPL-Umgebung ausführen:

 >>> x = 5 >>> id(x) 94529957049376 

Hier setzen wir die Variable x auf 5 . Wenn Sie versuchen, den Wert mithilfe von Addition zu ändern, erhalten Sie ein neues Objekt:

 >>> x += 1 >>> x 6 >>> id(x) 94529957049408 

Obwohl es den Anschein hat, dass dieser Code einfach den Wert von x ändert, erhalten Sie in Wirklichkeit ein neues Objekt als Antwort.

Der str Typ ist ebenfalls unveränderlich:

 >>> s = "real_python" >>> id(s) 140637819584048 >>> s += "_rocks" >>> s 'real_python_rocks' >>> id(s) 140637819609424 

Und in diesem Fall erhält s nach Operation += eine andere Speicheradresse.

Bonus : Der Operator += übersetzt in verschiedene Methodenaufrufe.

Bei einigen Objekten, z. B. einer Liste, wird += in __iadd__() konvertiert (lokaler Anhang). Es ändert sich selbst und gibt dieselbe ID zurück. str und int verfügen jedoch nicht über diese Methoden. Daher wird __add__() anstelle von __iadd__() aufgerufen.

Weitere Informationen finden Sie in der Dokumentation zum Python- Datenmodell .

Wenn wir versuchen, den Zeichenfolgenwert von s direkt zu ändern s wir eine Fehlermeldung:

 >>> s[0] = "R" 

Rückverfolgung (die letzten Anrufe werden zuletzt angezeigt):

  File "<stdin>", line 1, in <mdule> TypeError: 'str' object does not support item assignment 

Der obige Code stürzt ab und Python meldet, dass str diese Änderung nicht unterstützt, was der Definition der Unveränderlichkeit vom Typ str .

Vergleichen Sie mit einem veränderlichen Objekt, zum Beispiel mit einer Liste:

 >>> my_list = [1, 2, 3] >>> id(my_list) 140637819575368 >>> my_list.append(4) >>> my_list [1, 2, 3, 4] >>> id(my_list) 140637819575368 

Dieser Code zeigt den Hauptunterschied zwischen den beiden Objekttypen. Zu Beginn hat my_list eine ID. Auch nach dem Hinzufügen von 4 zur Liste hat my_list dieselbe ID. Der Grund ist, dass die Typliste veränderbar ist.

Hier ist eine weitere Demonstration der Listenveränderlichkeit mithilfe der Zuweisung:

 >>> my_list[0] = 0 >>> my_list [0, 2, 3, 4] >>> id(my_list) 140637819575368 

In diesem Code haben wir my_list geändert und als erstes Element auf 0 . Die Liste behielt jedoch nach diesem Vorgang dieselbe ID bei. Der nächste Schritt auf unserem Weg zum Erlernen von Python wird die Erforschung seines Ökosystems sein.

Wir beschäftigen uns mit Variablen


Variablen in Python unterscheiden sich grundlegend von Variablen in C und C ++. Im Wesentlichen existieren sie in Python einfach nicht. Anstelle von Variablen gibt es Namen .

Es mag pedantisch klingen und ist es größtenteils. Meistens können Sie Namen in Python als Variablen verwenden, aber Sie müssen den Unterschied verstehen. Dies ist besonders wichtig, wenn Sie ein so schwieriges Thema wie Zeiger studieren.

Um Ihnen das Verständnis zu erleichtern, sehen wir uns an, wie Variablen in C funktionieren, was sie darstellen, und vergleichen sie dann mit der Arbeit von Namen in Python.

Variablen in C.


Nehmen Sie den Code, der die Variable x definiert:

 int x = 2337; 

Die Ausführung dieser kurzen Zeile durchläuft verschiedene Phasen:

  1. Zuweisen von genügend Speicher für eine Nummer.
  2. Zuordnung von 2337 zu diesem Speicherort.
  3. Die Zuordnung x gibt diesen Wert an.

Ein vereinfachter Speicher könnte folgendermaßen aussehen:



Hier hat die Variable x eine gefälschte Adresse von 0x7f1 und einen Wert von 2337 . Wenn Sie später den Wert von x ändern möchten, können Sie dies tun:

 x = 2338; 

Dieser Code setzt die Variable x neuen Wert von 2338 , wodurch der vorherige Wert überschrieben wird. Dies bedeutet, dass die Variable x veränderbar ist . Aktualisiertes Speicherschema für den neuen Wert:



Bitte beachten Sie, dass sich die Position von x nicht geändert x sondern nur der Wert selbst. Es ist wichtig. Dies sagt uns, dass x ein Ort im Gedächtnis ist und nicht nur ein Name.

Sie können dieses Problem auch als Teil des Eigentumskonzepts betrachten. Einerseits besitzt x einen Speicherplatz. Erstens ist x ein leeres Feld, das nur eine Ganzzahl enthalten kann, in der Ganzzahlwerte gespeichert werden können.

Wenn Sie x einen Wert zuweisen, geben Sie den Wert in ein Feld ein, das zu x . Wenn Sie eine neue Variable y einführen möchten, können Sie diese Zeile hinzufügen:

 int y = x; 

Dieser Code erstellt ein neues Feld mit dem Namen y und kopiert den Wert von x in dieses Feld. Jetzt sieht die Speicherschaltung folgendermaßen aus:



Beachten Sie die neue Position y - 0x7f5 . Obwohl der Wert x nach x kopiert wurde, besitzt die Variable y eine neue Adresse im Speicher. Daher können Sie den Wert von y überschreiben, ohne x zu beeinflussen:

 y = 2339; 

Jetzt sieht die Speicherschaltung folgendermaßen aus:



Ich wiederhole: Sie haben den Wert von y geändert, aber nicht den Ort. Außerdem haben Sie die ursprüngliche Variable x nicht beeinflusst.

Bei Namen in Python ist die Situation völlig anders.

Namen in Python


In Python gibt es keine Variablen, stattdessen Namen. Sie können den Begriff „Variablen“ nach eigenem Ermessen verwenden. Es ist jedoch wichtig, den Unterschied zwischen Variablen und Namen zu kennen.

Nehmen wir den entsprechenden Code aus dem obigen C-Beispiel und schreiben ihn in Python:

 >>> x = 2337 

Wie in C durchläuft der Code während der Ausführung mehrere separate Schritte:

  1. PyObject wird erstellt.
  2. Der Nummer für PyObject wird ein Typcode zugewiesen.
  3. 2337 ein Wert für PyObject zugewiesen.
  4. Der Name x wird erstellt.
  5. x zeigt auf das neue PyObject.
  6. Der Referenzzähler von PyObject wird um 1 erhöht.

Hinweis : PyObject ist nicht dasselbe wie ein Objekt in Python. Diese Entität ist spezifisch für CPython und repräsentiert die Grundstruktur aller Python-Objekte.

PyObject ist als C-Struktur definiert. Wenn Sie sich also fragen, warum Sie den Typcode oder den Referenzzähler nicht direkt aufrufen können, liegt der Grund darin, dass Sie keinen direkten Zugriff auf die Strukturen haben. Das Aufrufen von Methoden wie sys.getrefcount () kann dabei helfen, interne Inhalte abzurufen .

Wenn wir über Erinnerung sprechen, kann es so aussehen:



Hier unterscheidet sich die Speicherschaltung stark von der oben gezeigten Schaltung in C. Anstatt x einen Speicherblock zu besitzen, in dem der Wert 2337 gespeichert ist, besitzt ein frisch erstelltes Python-Objekt den Speicher, in dem 2337 lebt. Der Python-Name x besitzt keine Adresse im Speicher direkt, genau wie eine C-Variable eine statische Zelle besitzt.

Wenn Sie x neuen Wert zuweisen möchten, versuchen Sie diesen Code:

 >>> x = 2338 

Das Verhalten des Systems unterscheidet sich von dem in C, unterscheidet sich jedoch nicht zu stark von der ursprünglichen Bindung in Python.

In diesem Code:

  • Ein neues PyObject wird erstellt.
  • Der Nummer für PyObject wird ein Typcode zugewiesen.
  • 2 ein Wert für PyObject zugewiesen.
  • x zeigt auf das neue PyObject.
  • Der Referenzzähler des neuen PyObject wird um 1 erhöht.
  • Der Referenzzähler des alten PyObjects wird um 1 reduziert.

Jetzt sieht die Speicherschaltung folgendermaßen aus:



Diese Abbildung zeigt, dass x auf eine Referenz auf ein Objekt verweist und den Speicherbereich nicht wie zuvor besitzt. Sie sehen auch, dass der Befehl x = 2338 keine Zuweisung ist, sondern eine Bindung des Namens x an den Link.

Außerdem befindet sich das vorherige Objekt (das den Wert 2337 ) jetzt mit einem Referenzzähler von 0 im Speicher und wird vom Garbage Collector entfernt .

Sie können einen neuen Namen y eingeben, wie im C-Beispiel:

 >>> y = x 

Ein neuer Name wird im Speicher angezeigt, aber nicht unbedingt ein neues Objekt:



Jetzt sehen Sie, dass kein neues Python-Objekt erstellt wurde, sondern nur ein neuer Name, der auf dasselbe Objekt verweist. Außerdem wurde der Objektreferenzzähler um 1 erhöht. Sie können die Äquivalenz der Identität von Objekten überprüfen, um deren Identität zu bestätigen:

 >>> y is x True 

Dieser Code zeigt, dass x und y ein Objekt sind. Aber machen Sie keinen Fehler: y ist immer noch unveränderlich. Sie können beispielsweise eine Additionsoperation mit y ausführen:

 >>> y += 1 >>> y is x False 

Nachdem der Zusatz aufgerufen wurde, geben Sie ein neues Python-Objekt zurück. Jetzt sieht die Erinnerung so aus:



Ein neues Objekt wurde erstellt und y jetzt darauf. Es ist merkwürdig, dass wir genau den gleichen Endzustand erhalten würden, wenn wir y direkt mit 2339 verknüpfen würden:

 >>> y = 2339 

Nach diesem Ausdruck erhalten wir einen solchen endgültigen Speicherzustand wie bei der Additionsoperation. Ich möchte Sie daran erinnern, dass Sie in Python keine Variablen zuweisen, sondern Namen an Links binden.

Über Praktikanten in Python


Jetzt verstehen Sie, wie neue Objekte in Python erstellt werden und wie Namen an sie angehängt werden. Es ist Zeit, über internierte Objekte zu sprechen.

Wir haben diesen Python-Code:

 >>> x = 1000 >>> y = 1000 >>> x is y True 

Nach wie vor sind x und y Namen, die auf dasselbe Python-Objekt verweisen. Dieses Objekt mit dem Wert 1000 kann jedoch nicht immer dieselbe Speicheradresse haben. Wenn Sie beispielsweise zwei Zahlen addieren und 1000 erhalten, erhalten Sie eine andere Adresse:

 >>> x = 1000 >>> y = 499 + 501 >>> x is y False 

Diesmal gibt die Zeichenfolge x is y False . Wenn Sie sich schämen, machen Sie sich keine Sorgen. Folgendes passiert, wenn dieser Code ausgeführt wird:

  1. Ein Python-Objekt wird erstellt ( 1000 ).
  2. Es wird der Name x .
  3. Ein Python-Objekt wird erstellt ( 499 ).
  4. Ein Python-Objekt wird erstellt ( 501 ).
  5. Diese beiden Objekte summieren sich.
  6. Ein neues Python-Objekt wird erstellt ( 1000 ).
  7. Er erhält den Namen y .

Technische Erläuterungen : Die beschriebenen Schritte finden nur statt, wenn dieser Code innerhalb der REPL ausgeführt wird. Wenn Sie das obige Beispiel nehmen, es in die Datei einfügen und ausführen, gibt die Zeile x is y True .

Der Grund ist der schnelle Verstand des CPython-Compilers, der versucht, Gucklochoptimierungen durchzuführen, um die Codeausführungsschritte so weit wie möglich zu speichern. Details finden Sie im Quellcode des Peyphole Optimizer CPython .

Aber ist das nicht verschwenderisch? Ja, aber Sie zahlen diesen Preis für alle Vorteile von Python. Sie müssen nicht daran denken, solche Zwischenobjekte zu löschen, und Sie müssen nicht einmal über ihre Existenz Bescheid wissen! Der Witz ist, dass diese Operationen relativ schnell ausgeführt werden und Sie bis zu diesem Moment nichts davon wissen würden.

Die Entwickler von Python bemerkten diesen Aufwand mit Bedacht und beschlossen, mehrere Optimierungen vorzunehmen. Ihr Ergebnis ist ein Verhalten, das Anfänger überraschen kann:

 >>> x = 20 >>> y = 19 + 1 >>> x is y True 

In diesem Beispiel ist der Code fast der gleiche wie oben, außer dass wir True . Es geht um internierte Objekte. Python erstellt eine bestimmte Teilmenge von Objekten im Speicher vorab und speichert sie für den täglichen Gebrauch im globalen Namespace.

Welche Objekte hängen von der Python-Implementierung ab? In CPython 3.7 sind Internierte:

  1. Ganzzahlen im Bereich von -5 bis 256 .
  2. Zeichenfolgen, die nur ASCII-Buchstaben, Zahlen oder Unterstriche enthalten.

Dies liegt daran, dass diese Variablen in vielen Programmen sehr häufig verwendet werden. Durch das Internieren verhindert Python die Speicherzuweisung für persistente Objekte.

Zeilen mit einer Größe von weniger als 20 Zeichen und ASCII-Buchstaben, -Zahlen oder -Unterstrichen werden interniert, da sie als Bezeichner verwendet werden sollen:

 >>> s1 = "realpython" >>> id(s1) 140696485006960 >>> s2 = "realpython" >>> id(s2) 140696485006960 >>> s1 is s2 True 

Hier zeigen s1 und s2 auf dieselbe Adresse im Speicher. Wenn wir keinen ASCII-Buchstaben, keine ASCII-Zahl oder keinen Unterstrich einfügen würden, würden wir ein anderes Ergebnis erhalten:

 >>> s1 = "Real Python!" >>> s2 = "Real Python!" >>> s1 is s2 False 

In diesem Beispiel wird ein Ausrufezeichen verwendet, sodass die Zeichenfolgen nicht interniert sind und unterschiedliche Objekte im Speicher sind.

Bonus : Wenn diese Objekte auf dasselbe internierte Objekt verweisen sollen, können Sie sys.intern() . Eine Möglichkeit zur Verwendung dieser Funktion ist in der Dokumentation beschrieben:

Das Internieren von Zeichenfolgen ist nützlich, um die Leistung der Wörterbuchsuche leicht zu steigern: Wenn die Schlüssel im Wörterbuch und der zu durchsuchende Schlüssel interniert sind, können Schlüsselvergleiche (nach dem Hashing) durch Vergleichen von Zeigern anstelle von Zeichenfolgen durchgeführt werden. ( Quelle )

Internierte verwirren Programmierer oft. Denken Sie daran, dass Sie im Zweifelsfall immer id() , um die Äquivalenz von Objekten zu bestimmen.

Python-Zeigeremulation


Die Tatsache, dass Zeiger in Python nativ fehlen, bedeutet nicht, dass Sie Zeiger nicht nutzen können. Es gibt tatsächlich mehrere Möglichkeiten, Zeiger in Python zu emulieren. Hier sehen wir uns zwei davon an:

  1. Verwendung als Zeiger auf veränderbare Typen.
  2. Verwenden speziell vorbereiteter Python-Objekte.

Verwendung als veränderbare Typzeiger


Sie wissen bereits, was veränderbare Typen sind. Dank ihrer Veränderlichkeit können wir das Verhalten von Zeigern emulieren. Angenommen, Sie müssen diesen Code replizieren:

 void add_one(int *x) { *x += 1; } 

Dieser Code nimmt einen Zeiger auf eine Zahl ( *x ) und erhöht den Wert um 1. Hier ist die Hauptfunktion zum Ausführen des Codes:

 #include <stdi.h> int main(void) { int y = 2337; printf("y = %d\n", y); add_one(&y); printf("y = %d\n", y); return 0; } 

Im obigen Fragment haben wir 2337 y zugewiesen, den aktuellen Wert angezeigt, um 1 erhöht und dann einen neuen Wert angezeigt. Auf dem Bildschirm wird Folgendes angezeigt:

 y = 2337 y = 2338 

Eine Möglichkeit, dieses Verhalten in Python zu replizieren, besteht darin, einen veränderlichen Typ zu verwenden. Wenden Sie beispielsweise eine Liste an und ändern Sie das erste Element:

 >>> def add_one(x): ... x[0] += 1 ... >>> y = [2337] >>> add_one(y) >>> y[0] 2338 

Hier bezieht sich add_one(x) auf das erste Element und erhöht seinen Wert um 1. Die Verwendung der Liste bedeutet, dass wir als Ergebnis den geänderten Wert erhalten. Es gibt also Zeiger in Python? Nein. Das beschriebene Verhalten wurde möglich, weil die Liste ein veränderbarer Typ ist. Wenn Sie versuchen, ein Tupel zu verwenden, wird folgende Fehlermeldung angezeigt:

 >>> z = (2337,) >>> add_one(z) 

Rückverfolgung (die letzten Anrufe gehen zuletzt):

  File "<stdin>", line 1, in <module> File "<stdin>", line 2, in add_one TypeError: 'tuple' object does not support item assignment 

Dieser Code demonstriert die Unveränderlichkeit des Tupels und unterstützt daher keine Elementzuweisung.

list nicht der einzige veränderbare Typ, Teilzeiger werden auch mit dict emuliert.

Angenommen, Sie haben eine Anwendung, die das Auftreten interessanter Ereignisse verfolgen soll. Dies kann erreicht werden, indem ein Wörterbuch erstellt und eines seiner Elemente als Zähler verwendet wird:

 >>> counters = {"func_calls": 0} >>> def bar(): ... counters["func_calls"] += 1 ... >>> def foo(): ... counters["func_calls"] += 1 ... bar() ... >>> foo() >>> counters["func_calls"] 2 

In diesem Beispiel verwendet das Wörterbuch Zähler, um die Anzahl der Funktionsaufrufe zu verfolgen. Nach dem Aufruf von foo() erhöhte sich foo() Zähler erwartungsgemäß um 2. Und das alles dank dict .

Vergessen Sie nicht, dies ist nur eine Emulation des Zeigerverhaltens, es hat nichts mit echten Zeigern in C und C ++ zu tun. Wir können sagen, dass diese Operationen teurer sind als wenn sie in C oder C ++ ausgeführt würden.

Verwenden von Python-Objekten


dict ist eine großartige Möglichkeit, Zeiger in Python zu emulieren, aber es ist manchmal mühsam, sich zu merken, welchen Schlüsselnamen Sie verwendet haben. Insbesondere, wenn Sie das Wörterbuch in verschiedenen Teilen der Anwendung verwenden. Eine benutzerdefinierte Python-Klasse kann hier helfen.

Angenommen, Sie müssen Metriken in einer Anwendung verfolgen. Eine gute Möglichkeit, nervige Details zu ignorieren, besteht darin, eine Klasse zu erstellen:

 class Metrics(object): def __init__(self): self._metrics = { "func_calls": 0, "cat_pictures_served": 0, } 

Dieser Code definiert die Metrics Klasse. Das Wörterbuch wird weiterhin zum Speichern aktueller Daten verwendet, die in der Mitgliedsvariablen _metrics . Dies gibt Ihnen die erforderliche Veränderlichkeit. Jetzt müssen Sie nur noch auf diese Werte zugreifen. Sie können dies mit den folgenden Eigenschaften tun:

 class Metrics(object): # ... @property def func_calls(self): return self._metrics["func_calls"] @property def cat_pictures_served(self): return self._metrics["cat_pictures_served"] 

Hier verwenden wir @property . Wenn Sie mit Dekorateuren noch nicht vertraut sind, lesen Sie den Artikel Primer on Python Decorators . In diesem Fall können Sie mit dem @property Dekorator auf func_calls und cat_pictures_served , als wären sie Attribute:

 >>> metrics = Metrics() >>> metrics.func_calls 0 >>> metrics.cat_pictures_served 0 

Die Tatsache, dass Sie diese Namen als Attribute bezeichnen können, bedeutet, dass Sie von der Tatsache abstrahiert sind, dass diese Werte im Wörterbuch gespeichert sind. Außerdem machen Sie Attributnamen expliziter. Natürlich sollten Sie in der Lage sein, die Werte zu erhöhen:

 class Metrics(object): # ... def inc_func_calls(self): self._metrics["func_calls"] += 1 def inc_cat_pics(self): self._metrics["cat_pictures_served"] += 1 

:

  1. inc_func_calls()
  2. inc_cat_pics()

metrics . , , :

 >>> metrics = Metrics() >>> metrics.inc_func_calls() >>> metrics.inc_func_calls() >>> metrics.func_calls 2 

func_calls inc_func_calls() Python. , - metrics , .

: , inc_func_calls() inc_cat_pics() @property.setter int , .

Metrics :

 class Metrics(object): def __init__(self): self._metrics = { "func_calls": 0, "cat_pictures_served": 0, } @property def func_calls(self): return self._metrics["func_calls"] @property def cat_pictures_served(self): return self._metrics["cat_pictures_served"] def inc_func_calls(self): self._metrics["func_calls"] += 1 def inc_cat_pics(self): self._metrics["cat_pictures_served"] += 1 

ctypes


, - Python, CPython? ctypes , C. ctypes, Extending Python With C Libraries and the «ctypes» Module .

, , . - add_one() :

 void add_one(int *x) { *x += 1; } 

, x 1. , (shared) . , add.c , gcc:

 $ gcc -c -Wall -Werror -fpic add.c $ gcc -shared -o libadd1.so add.o 

C add.o . libadd1.so .

libadd1.so . ctypes Python:

 >>> import ctypes >>> add_lib = ctypes.CDLL("./libadd1.so") >>> add_lib.add_one <_FuncPtr object at 0x7f9f3b8852a0> 

ctypes.CDLL , libadd1 . add_one() , , Python-. , . Python , .

, ctypes :

 >>> add_one = add_lib.add_one >>> add_one.argtypes = [ctypes.POINTER(ctypes.c_int)] 

, C. , , :

 >>> add_one(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> ctypes.ArgumentError: argument 1: <class 'TypeError'>: \ expected LP_c_int instance instead of int 

Python , add_one() , . , ctypes . :

 >>> x = ctypes.c_int() >>> x c_int(0) 

x 0 . ctypes byref() , .

: .

, . , .

add_one() :

 >>> add_one(ctypes.byref(x)) 998793640 >>> x c_int(1) 

Großartig! 1. , Python .

Fazit


Python . , Python.

Python:

  • .
  • Python- .
  • ctypes.

Python .

Source: https://habr.com/ru/post/de454324/


All Articles