Eine Geschichte über den Versuch, private Variablen in Python zu implementieren.

Letzte Zusammenfassung

Im Rahmen eines Vortrags über die Erforschung umfangreicher Software habe ich mich entschlossen, Python zu erforschen, an dem ich mich schon seit einiger Zeit interessiert hatte. Da die Aufgabe der Wiederbelebung der python2-Druckanweisung erfüllt ist, werden wir uns diesmal mit der Aufgabe befassen, eine private Variable in Python einzuführen.

Hintergrund

** Private Variable existiert nicht in Python-Klasse **. Stattdessen ist es üblich, eine Variable als private Variable zu behandeln, indem ihr ein "_" vorangestellt wird. Es bleibt dem Gewissen des Programmierers überlassen. Dies ist eine ganz andere Idee als objektorientierte Sprachen wie Java. Der Hintergrund ist der folgende Stapelüberlaufpfosten http://stackoverflow.com/questions/1641219/does-python-have-private-variables-in-classes Bitte beziehen Sie sich auf. Ich dachte jedoch, dass es einen neuen Programmierer geben könnte, der den Brauch von Python nicht kennt und Variablen neu schreibt, die nicht von außerhalb der Klasse neu geschrieben werden sollten (ohne Erlaubnis), daher ist mir die Idee von Python diesmal egal, und es ist eine private Variable. Ich wollte die Konvention strenger umsetzen.

Problem Definition

Die ursprüngliche private Variable soll einen Fehler auslösen und die Verarbeitung stoppen, wenn (wahrscheinlich) von außen darauf zugegriffen wird. Wie Sie jedoch sehen können, gibt es in der internen Verarbeitung von Python verschiedene Prozesse, um von außen auf private Variablen zuzugreifen. Wenn Sie also versuchen, mit einem Fehler zu scheitern, fällt Python ohne Erlaubnis aus (oder wird nicht kompiliert). Hmm). Daher habe ich mich dieses Mal entschlossen, es in Form einer Warnung beim Zugriff auf die private Variable zu implementieren. Ich frage mich, ob ich eine private Variable eingeführt habe, aber ich dachte, dies würde ausreichen, um das Ziel von Benutzern zu erreichen, die die Python-Konventionen nicht kannten, also habe ich einen Kompromiss geschlossen.

Fummelei

Erstens, wenn Sie das Problem unterteilen, an dem Sie gerade arbeiten

  1. Erfassen Sie den Zeitpunkt des Zugriffs auf Klassenvariablen
  2. Überprüfen Sie, ob der Variablenname am Anfang "_" hat
  3. Stellen Sie sicher, dass das Ziel, auf das Sie zugreifen möchten, ein Objekt ist, das diese Variable als Instanzvariable hat. Es kann in drei Prozesse unterteilt werden.

Ich habe mich vom letzten Mal an auf GDB konzentriert, um das herauszufinden.

Erfassen Sie den Zeitpunkt des Zugriffs auf Variablen

Zuerst habe ich nach dem Compiler gesucht, weil ich beim letzten Spiel mit dem Compiler mitgerissen wurde. Aber wenn Sie darüber nachdenken, sollte diese Methode nicht funktionieren. Dies liegt daran, dass die Python-Klasse schließlich nur ein Wörterbuch ist und zum Zeitpunkt der Kompilierung nicht immer bekannt ist, über welchen Schlüssel (Mitgliedsvariable) sie verfügt. Daher greifen Sie zum Zeitpunkt der Kompilierung nicht auf die Informationen der Mitgliedsvariablen der Instanz zu, und Sie können nur durch Zugriff auf die Variablen feststellen, ob die Mitgliedsvariablen vorhanden sind. Mit anderen Worten, Sie müssen wirklich untersuchen, wie Sie mit dem Zeitpunkt umgehen, zu dem auf eine Mitgliedsvariable basierend auf dem kompilierten Operationscode tatsächlich zugegriffen wird. Sie suchen also nach der virtuellen Maschine von Python, nicht nach dem Compiler. Nach ein wenig Recherche stellte ich fest, dass eine Datei namens ceval.c den Operationscode auswertet. Die Verarbeitung wird auf der folgenden Website ausführlich beschrieben. http://www.nasuinfo.or.jp/FreeSpace/kenji/sf/python/virtualMachine/PyVM.html

Als nächstes verwendete ich dis, ein praktisches integriertes Python-Modul, um zu bestimmen, welcher Operationscode angezeigt werden soll. dis konvertiert den übergebenen Python-Code in den im Python-Interpreter verwendeten Operationscode, sodass ich den Operationscode für den folgenden Code generiert habe.

Vor der Konvertierung


class Foo:
    def __init__(self, foo):
        self._foo = foo
    def mymethod(self, foo):
        self._foo = foo

f = Foo("constructor")
f._foo = "external access"
f.mymethod("internal access")

Nach der Konvertierung


  2           0 LOAD_BUILD_CLASS
              1 LOAD_CONST               1 (<code object Foo at 0x118a0bdb0,line 2>)
              4 LOAD_CONST               2 ('Foo')
              7 MAKE_FUNCTION            0
             10 LOAD_CONST               2 ('Foo')
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             16 STORE_FAST               0 (Foo)

  8          19 LOAD_FAST                0 (Foo)
             22 LOAD_CONST               3 ('constructor')
             25 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             28 STORE_FAST               1 (f)

  9          31 LOAD_CONST               4 ('external access')
             34 LOAD_FAST                1 (f)
             37 STORE_ATTR               0 (_foo)

 10          40 LOAD_FAST                1 (f)
             43 LOAD_ATTR                1 (mymethod)
             46 LOAD_CONST               5 ('internal access')
             49 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             52 POP_TOP
             53 LOAD_CONST               0 (None)
             56 RETURN_VALUE

Als ich es sah, stellte ich fest, dass der Operationscode STORE_ATTR verdächtig war. Damit sind die Materialien vorerst fertig. Von hier aus werden wir ceval.c unter Verwendung von gdb erkunden.

Untersuchen Sie den Zugriff auf Variablen

Als ich ehrlich nach der Zeichenfolge STORE_ATTR in ceval.c suchte, fand ich zuerst den folgenden Teil.

TARGET(STORE_ATTR) {
       PyObject *name = GETITEM(names, oparg);
       PyObject *owner = TOP();
       PyObject *v = SECOND();
       int err;
       STACKADJ(-2);
       err = PyObject_SetAttr(owner, name, v);
       Py_DECREF(v);
       Py_DECREF(owner);
       if (err != 0)
           goto error;
       DISPATCH();
}

Es scheint, dass wir hier den STORE_ATTR-Operationscode verarbeiten. Und es gab einige Dinge, die ich verstehen konnte, wenn ich mir diesen Code ansah. Zunächst kann aus dem Variablennamen abgeleitet werden, dass der Name ** der Name der Variablen ist, auf die Sie zugreifen möchten ** und der Eigentümer die Instanz ** ist, die Sie festlegen sollten **. Und es scheint, dass die Funktion, die die Variable tatsächlich setzt, PyObject_SetAttr ist. Die Argumente der PyObject_SetAttr-Funktion sind name, owner, v, daher wird angenommen, dass v wahrscheinlich der Wert ist, den Sie festlegen möchten. Um diese Hypothesen zu bestätigen, habe ich hier vorerst einen Haltepunkt gesetzt und den folgenden Code test.py mit gdb ausgeführt.

test.py


class Foo:
    def __init__(self, foo):
        self._foo = foo
    def mymethod(self, foo):
        self._foo = foo

f = Foo("constructor")
f._foo = "external access"
f.mymethod("internal access")

Wenn ich es jedoch tatsächlich versuche, scheint es, dass Python STORE_ATTR auch beim Laden von Modulen stark nutzt. Wenn dies der Fall ist, wird zu viel Verarbeitung abgefangen und die Arbeit wird nicht fortgesetzt. Wenn Sie es nicht nur stoppen, wenn das Attribut "_foo" aufgerufen wird, können Sie es nicht berühren. Das Problem ist, dass der Name vom Typ PyObject ist, ein Python-Objekt (diesmal Unicode), sodass Sie einfach nicht auf den Variablennamen zugreifen können. Daher habe ich die in unicodeobject.h und unicodeobject.c beschriebene Implementierung von Python vom Typ Unicode untersucht, um festzustellen, ob sie in eine C-Zeichenfolge (char-Array) konvertiert werden kann. Dann gab es in unicodeobject.h eine solche Funktion.


#ifndef Py_LIMITED_API
PyAPI_FUNC(char *) PyUnicode_AsUTF8(PyObject *unicode);
#define _PyUnicode_AsString PyUnicode_AsUTF8
#endif

Als ich diese Funktion als Test auf das Namensobjekt anwendete, konnte ich die Zeichenfolge, auf die der Name zeigt, als char * -Typ abrufen. Auf diese Weise habe ich einen Haltepunkt in der if-Anweisung festgelegt, der unter der Bedingung gilt, dass Name und "_foo" übereinstimmen, sodass gdb nur dann stoppt, wenn der Name "_foo" lautet, und ich habe versucht, gdb auszuführen. ,Es ging gut. Hier können wir sicherlich die Hypothese beweisen, dass name der Name der Variablen ist, auf die wir tatsächlich zugreifen möchten.

Als Nächstes haben wir uns die Verarbeitung der PyObject_SetAttr-Funktion angesehen und die Hypothese getestet, dass der Eigentümer tatsächlich die Instanz ist, die der Eigentümer festlegen möchte, und v der Wert, den der Eigentümer festlegen möchte. Nachdem ich eine Weile getaucht hatte, stellte ich fest, dass die folgende Funktion mit dem Eigentümer als erstem Argument aufgerufen wurde.


int
_PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
                                 PyObject *value, PyObject *dict)
{
    PyTypeObject *tp = Py_TYPE(obj);
    PyObject *descr;
    descrsetfunc f;
    PyObject **dictptr;
    int res = -1;

    if (!PyUnicode_Check(name)){
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     name->ob_type->tp_name);
        return -1;
    }

    if (tp->tp_dict == NULL && PyType_Ready(tp) < 0)
        return -1;

    Py_INCREF(name);

    descr = _PyType_Lookup(tp, name);
    Py_XINCREF(descr);

    f = NULL;
    if (descr != NULL) {
        f = descr->ob_type->tp_descr_set;
        if (f != NULL && PyDescr_IsData(descr)) {
            res = f(descr, obj, value);
            goto done;
        }
    }

    if (dict == NULL) {
        dictptr = _PyObject_GetDictPtr(obj);
        if (dictptr != NULL) {
            res = _PyObjectDict_SetItem(Py_TYPE(obj), dictptr, name, value);
            if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
                PyErr_SetObject(PyExc_AttributeError, name);
            goto done;
        }
    }
    if (dict != NULL) {
        Py_INCREF(dict);
        if (value == NULL)
            res = PyDict_DelItem(dict, name);
        else
            res = PyDict_SetItem(dict, name, value);
        Py_DECREF(dict);
        if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
            PyErr_SetObject(PyExc_AttributeError, name);
        goto done;
    }

    if (f != NULL) {
        res = f(descr, obj, value);
        goto done;
    }

    if (descr == NULL) {
        PyErr_Format(PyExc_AttributeError,
                     "'%.100s' object has no attribute '%U'",
                     tp->tp_name, name);
        goto done;
    }

    PyErr_Format(PyExc_AttributeError,
                 "'%.50s' object attribute '%U' is read-only",
                 tp->tp_name, name);
  done:
    Py_XDECREF(descr);
    Py_DECREF(name);
    return res;
}

Wichtig ist, hier das Wörterbuch des ersten Arguments (obj) zu erhalten. Da eine Python-Klasseninstanz im Grunde ein Wörterbuch ist, bedeutet dies, dass Sie auf obj selbst zugreifen. Mit anderen Worten, wir konnten bestätigen, dass der Eigentümer tatsächlich die Instanz war, auf die zugegriffen werden soll. Und da Sie sehen können, dass v im Wörterbuch mit dem Schlüsselnamen name mit PyDict_SetItem festgelegt ist, können Sie auch bestätigen, dass v der festzulegende Wert ist.

Von hier aus müssen Sie also die Informationen der ** aufrufenden Instanz ** herausfinden und sie mit dem Eigentümer abgleichen (vergleichen Sie den Zeiger). Also suchte ich nach Informationen über die aufrufende Instanz.

Hier finden Sie Informationen zu einer Instanz

Bei Python-Methoden wird die Instanz selbst immer auf das erste Argument gesetzt. Mit anderen Worten, Sie sollten in der Lage sein, die Instanzinformationen aus der Methode zurückzurechnen. Die Methode bezog sich auf die offizielle Dokumentation, um herauszufinden, zu welcher Instanz sie gehört. https://docs.python.org/3/c-api/method.html Anscheinend ruft eine Funktion namens PyMethod_GET_SELF die Instanz ab, zu der sie gehört. Eine weitere Sache, die ich herausgefunden habe, ist, dass eine Methode im Grunde nur eine Struktur ist, die Informationen über die auszuführende Funktion und die Instanz enthält, zu der sie gehört. Dies wird später wichtig. Basierend auf diesen Informationen habe ich test.py erneut mit gdb ausgeführt, um nach Instanzinformationen zu suchen. Zusammenfassend konnte ich keine Informationen über die Instanz finden, die die Methode aufruft, egal wie weit unten im Stapel. Und als ich die Stapelspur erklomm, fand ich heraus warum.

classobject.c


static PyObject *
method_call(PyObject *func, PyObject *arg, PyObject *kw)
{
    PyObject *self = PyMethod_GET_SELF(func);
    PyObject *result;

    func = PyMethod_GET_FUNCTION(func);
    if (self == NULL) {
        PyErr_BadInternalCall();
        return NULL;
    }
    else {
        Py_ssize_t argcount = PyTuple_Size(arg);
        PyObject *newarg = PyTuple_New(argcount + 1);
        int i;
        if (newarg == NULL)
            return NULL;
        Py_INCREF(self);
        PyTuple_SET_ITEM(newarg, 0, self);
        for (i = 0; i < argcount; i++) {
            PyObject *v = PyTuple_GET_ITEM(arg, i);
            Py_XINCREF(v);
            PyTuple_SET_ITEM(newarg, i+1, v);
        }
        arg = newarg;
    }
    result = PyObject_Call((PyObject *)func, arg, kw);
    if(PyDict_Contains(funcdict, PyUnicode_FromString("__parent_instance__"))){
        PyDict_DelItem(funcdict, PyUnicode_FromString("__parent_instance__"));
    }
    Py_DECREF(arg);
    return result;
}

Sie können sehen, dass der Methodenaufruf von der obigen Funktion ausgeführt zu werden scheint. Wenn Sie sich diesen Prozess genauer ansehen, können Sie feststellen, dass die Methodenaufrufe in den folgenden Schritten grob ausgeführt werden:

  1. Die Methode ist nur eine Kombination aus Funktions- und Instanzinformationen. Trennen Sie also Funktion und Instanz.
  2. Initialisieren Sie die an die Funktion übergebenen Argumente als Tapples
  3. Setzen Sie die Instanz im ersten Argument des Taple.
  4. Funktionsausführung

Denke nochmal nach. ** Instanzen sind nur Funktionsargumente. ** Dies bedeutet, dass zum Zeitpunkt der Ausführung der Methode durch method_call die Informationen über die Instanz, zu der die Methode gehört, bereits verloren gehen. Die Verarbeitung von STORE_ATTR wird ausgeführt, nachdem method_call aufgerufen wurde. Mit anderen Worten, wenn dies unverändert bleibt, kann zum Zeitpunkt des Zugriffs auf die Mitgliedsvariablen der Instanz nicht zwischen dem Zugriff durch externe Funktionen wie die folgenden und dem Zugriff durch interne Methoden unterschieden werden.

class Foo:
	def inner_method(self, foo):
		self._foo = foo

def outer_function(hoge, foo):
	hoge._foo = foo

f = Foo()

f.inner_method(100)

outer_function(f, 100)

Das ist ein Problem. Irgendwie müssen wir die Informationen der Aufruferinstanz an den Prozess übergeben, der STORE_ATTR ausführt. In diesem Fall haben Sie keine andere Wahl, als es gewaltsam zu übergeben.

Instanzinformationen übergeben

Die Methode, die ich dieses Mal verwendet habe, besteht darin, die Instanz in der globalen Variablen mit dem Variablennamen "\ _ \ _ parent_instance \ _ \ _" zum Zeitpunkt von method_call zwangsweise zu registrieren. Eigentlich sollte es in einem neuen Bereich definiert werden, aber es ist problematisch, also habe ich mich dieses Mal entschlossen, mit einer solchen Brute-Force-Methode durchzubrechen. Globale Variablen sind eine der Eigenschaften einer Funktion und werden als Python-Wörterbuch implementiert. Während ich die Implementierung des Wörterbuchtyps in dictobject.h und dictobject.c überprüfte, habe ich sie wie folgt in der globalen Variablen registriert.

Geändertes Klassenobjekt.c


static PyObject *
method_call(PyObject *func, PyObject *arg, PyObject *kw)
{
    PyObject *self = PyMethod_GET_SELF(func);
    PyObject *result;
    #if PRIVATE_ATTRIBUTE
    PyFunctionObject *temp;
    PyObject *funcdict;
    #endif

    func = PyMethod_GET_FUNCTION(func);
    if (self == NULL) {
        PyErr_BadInternalCall();
        return NULL;
    }
    else {
        Py_ssize_t argcount = PyTuple_Size(arg);
        PyObject *newarg = PyTuple_New(argcount + 1);
        int i;
        if (newarg == NULL)
            return NULL;
        Py_INCREF(self);
        PyTuple_SET_ITEM(newarg, 0, self);
        for (i = 0; i < argcount; i++) {
            PyObject *v = PyTuple_GET_ITEM(arg, i);
            Py_XINCREF(v);
            PyTuple_SET_ITEM(newarg, i+1, v);
        }
        arg = newarg;
    }
    #if PRIVATE_ATTRIBUTE
    temp = (PyFunctionObject *)func;
    funcdict = temp->func_globals;
    if(funcdict == NULL){
        funcdict = PyDict_New();
    }
    PyDict_SetItem(funcdict, PyUnicode_FromString("__parent_instance__"), self);
    #endif
    result = PyObject_Call((PyObject *)func, arg, kw);
    if(PyDict_Contains(funcdict, PyUnicode_FromString("__parent_instance__"))){
        PyDict_DelItem(funcdict, PyUnicode_FromString("__parent_instance__"));
    }
    Py_DECREF(arg);
    return result;
}

Der von "#if PRIVATE_ATTRIBUTE" und "# endif" eingeschlossene Teil ist der hinzugefügte Code. An der Funktion können Sie erkennen, dass das Wörterbuch, in dem die globalen Variablen registriert sind, abgerufen wird und die Variablen dort zwangsweise registriert werden. Wenn Sie \ _ \ _ parent_instance \ _ \ _ nach Abschluss der Ausführung der Methode nicht aus den globalen Variablen löschen, können Sie darauf zugreifen, sobald Sie über die interne Methode auf die private Variable zugreifen. Es ist geworden. Wenn die Ausführung der Methode abgeschlossen ist, löscht PyDict_DelItem schließlich \ _ \ _ parent_instance \ _ \ _.

Als nächstes wurde bei der Ausführung von STORE_ATTR die folgende Verarbeitung hinzugefügt und korrigiert.

Modifizierter Ceval.c


TARGET(STORE_ATTR) {
            PyObject *name = GETITEM(names, oparg);
            PyObject *owner = TOP();
            PyObject *v = SECOND();
            int err;
            #if PRIVATE_ATTRIBUTE
              char *name_as_cstr;
              PyObject *parent_instance;
              if(PyDict_Contains(f->f_globals, PyUnicode_FromString("__parent_instance__"))){
                parent_instance = PyDict_GetItem(f->f_globals, PyUnicode_FromString("__parent_instance__"));
              }else{
                parent_instance = NULL;
              }
            #endif
            STACKADJ(-2);

            #if PRIVATE_ATTRIBUTE
              name_as_cstr = _PyUnicode_AsString(name);
              if(name_as_cstr[0] == '_'){
                if(!parent_instance || (parent_instance - owner) != 0){
                  printf("Warning: Illegal access to a private attribute!\n");
                }
              }
            #endif

            err = PyObject_SetAttr(owner, name, v);
            Py_DECREF(v);
            Py_DECREF(owner);
            if (err != 0)
                goto error;
            DISPATCH();
        }

Wenn das erste Zeichen des Namens \ _ ist, wird festgestellt, dass das Zugriffsziel eine private Variable ist. Und wenn die globale Variable \ _ \ _ parent_instance \ _ \ _ enthält und der Eigentümer und \ _ \ _ parent_instance \ _ \ _ übereinstimmen, wird dies als legitimer Zugriff betrachtet, andernfalls wird die Warnung gedruckt. Ich habe einen einfachen Vorgang zum Anzeigen hinzugefügt. Und wenn ich versuche, mit PRIVATE_ATTRIBUTE zu kompilieren, das in config auf 1 gesetzt ist ...

python


>>> class Foo:
...     def __init__(self, foo):
...             self._foo = foo
...     def mymethod(self, foo):
...             self._foo = foo
...
>>>
>>>
>>> f = Foo(1)
>>> # no warnings!
>>>
>>> f._foo = 2
Warning: Attempting to access private attribute illegally

Der Konstruktor wird in der Zeile "f = Foo (1)" aufgerufen. Auf den ersten Blick scheint die Warnung hier nicht zu erscheinen und die Warnung tritt auf, wenn der Zugriff von außen erfolgt. Aber hier ist das Problem.

python


>>> f.mymethod(3)
Warning: Attempting to access private attribute illegally

Aus irgendeinem Grund war ich wütend, obwohl es ein Zugang von innen war. Wenn ich test.py auf gdb ausführe, um die Ursache herauszufinden, ist \ _ \ _ parent_instance \ _ \ _ nicht in der globalen Variablen registriert. Irgendwo funktioniert der Prozess nicht. Rückblickend auf den Stack-Trace stellte sich heraus, dass ** method_call in f.mymethod (3) nicht ** aufgerufen wurde. Stattdessen wurde eine mysteriöse Funktion namens fast_function aufgerufen.

Kämpfe mit fast_function

Als ich nachschaute, was fast_function ist, gab es auf der folgenden Seite eine nützliche Erklärung. http://eli.thegreenplace.net/2012/03/23/python-internals-how-callables-work

To understand what fast_function does, it's important to first consider what happens when a Python function is executed. Simply put, its code object is evaluated (with PyEval_EvalCodeEx itself). This code expects its arguments to be on the stack. Therefore, in most cases there's no point packing the arguments into containers and unpacking them again. With some care, they can just be left on the stack and a lot of precious CPU cycles can be spared.

Anscheinend ist es verschwenderisch, nacheinander eine Reihe von Argumenten zu erstellen. Es scheint also, dass Python versucht, durch das Abrufen von Variablen vom Stapel zu beschleunigen. Dafür ist die Funktion fast_function zuständig. Mit anderen Worten, es ist schneller, wenn method_call nicht durchlaufen wird. Die Methode sollte jedoch irgendwann noch aufgerufen werden und die Instanz sollte erworben worden sein. Wenn Sie es zu diesem Zeitpunkt in der globalen Variablen registrieren, sollten Sie in der Lage sein, dasselbe wie eine Erweiterung der vorherigen Implementierung zu erreichen. Als ich nach dem Teil suchte, der auf die Instanz zugreift, zu der die Methode gehört, dh nach dem Teil, in dem PyMethod_GET_SELF aufgerufen wird, wurde der folgende Teil der Funktion call_function von ceval.c getroffen.

call_innerhalb der Funktionsfunktion


if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
            /* optimize access to bound methods */
            PyObject *self = PyMethod_GET_SELF(func);
            PCALL(PCALL_METHOD);
            PCALL(PCALL_BOUND_METHOD);
            Py_INCREF(self);
            func = PyMethod_GET_FUNCTION(func);
            Py_INCREF(func);
            Py_SETREF(*pfunc, self);
            na++;
            n++;
}

Mit einer einfachen Idee habe ich sie wie folgt umgeschrieben.

Geänderter Anruf_Funktion Funktion


if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
            /* optimize access to bound methods */
            PyObject *self = PyMethod_GET_SELF(func);
            PCALL(PCALL_METHOD);
            PCALL(PCALL_BOUND_METHOD);
            Py_INCREF(self);
            func = PyMethod_GET_FUNCTION(func);
            Py_INCREF(func);
            Py_SETREF(*pfunc, self);
            na++;
            n++;
            #if PRIVATE_ATTRIBUTE
            temp = (PyFunctionObject *)func;
            funcdict = temp->func_globals;
            if(funcdict == NULL){
                funcdict = PyDict_New();
            }
            PyDict_SetItem(funcdict, PyUnicode_FromString("__parent_instance__"), self);
            #endif
}

Beachten Sie, dass temp und funcdict mit dem Typ und der Bedeutung identisch sind, als classobject.c geändert wurde. Wenn Sie es zu Beginn der Funktion nicht definieren, wird der Compiler wütend, sodass die Definition einfach nicht in diesem Code enthalten ist. Das Löschen wurde am Ende der Funktion wie folgt durchgeführt.


assert((x != NULL) ^ (PyErr_Occurred() != NULL));
    #if PRIVATE_ATTRIBUTE
    temp = (PyFunctionObject *)func;
    funcdict = temp->func_globals;
    if(funcdict == NULL){
        funcdict = PyDict_New();
    }
    if(PyDict_Contains(funcdict, PyUnicode_FromString("__parent_instance__"))){
        PyDict_DelItem(funcdict, PyUnicode_FromString("__parent_instance__"));
    }
    #endif
    return x;
}

Versuchen Sie nun, es zu kompilieren und auszuführen. Dann funktionierte es unter Mac OS wie folgt.

>>> class Foo:
...     def __init__(self, foo):
...             self._foo = foo
...     def mymethod(self, foo):
...             self._foo = foo
...
>>> f = Foo(10)
>>> f._foo = 100
Warning: Illegal access to a private attribute!
>>> f.mymethod(200)
>>>
>>> def outerfunc(f, foo):
...     f._foo = foo
...
>>> outerfunc(f, 100)
Warning: Illegal access to a private attribute!

Sie können sehen, dass mymethod keine Fehler auslöst. Es war überraschend einfach. Als ich jedoch versuchte, die Quelle mit denselben Änderungen unter Ubuntu zu kompilieren, war ich wütend, dass ich mich nicht mit der globalen Variablen anlegen sollte, und die Kompilierung schlug mit einem Fehler fehl. Ursprünglich sollte es so modifiziert werden, dass es unter Ubuntu funktioniert, aber diesmal hatte ich nicht so viel Zeit, also habe ich dies vorerst zu einem Kompromiss gemacht.

Was ich gelernt habe

Durch dieses Experiment habe ich einige wichtige Lektionen für das Spielen mit umfangreicher Software gelernt, daher werde ich es mündlich für mich selbst zusammenfassen.

  1. Sie sollten sich auf offizielle Unterlagen verlassen Dieses Mal gab es viele Szenen, in denen das Verständnis sofort durch Lesen des offiziellen Dokuments fortschritt. Das ist natürlich nicht der Fall, wenn die offizielle Dokumentation nicht vorhanden ist, aber ich denke, es ist eine gute Idee, die offizielle Dokumentation so schnell wie möglich zu lesen. Das ist selbstverständlich. Es ist jedoch schwierig, Dokumente zu finden, die sich auf die interne Verarbeitung vieler Software beziehen, nicht nur von Python, indem Sie einfach suchen. Der beste Weg, um die offizielle Dokumentation zu finden, ist die Verwendung der Schlüsselwörter "Mitwirkender" und "Entwickler". Wenn Sie nach einer umfangreichen Software suchen, suchen Sie nach "Software Name Contributor", "Software Name Developer Guide" usw. und finden Sie eine Anleitung für den Teil, den Sie beitragen möchten (Grammatik für Grammatik, Leistung für Leistung usw.). Ich denke, es ist eine gute Idee, zunächst danach zu suchen. Daher unterscheidet sich die Effizienz völlig zwischen dem Befolgen des Codes nach dem Erstellen eines Sterns und dem einfachen Befolgen des Codes.

  2. Die Wichtigkeit, den Prozess zu verbalisieren Ich denke, wenn Sie klar verbalisieren, was Sie tun möchten, wird klar, was Sie zum ersten Mal tun sollten. Zum Beispiel die Politik der Implementierung privater Variablen,

"Machen Sie die Variablen einer Instanz nur innerhalb dieser Instanz zugänglich"

ich meine

"Bestimmen Sie zum Zeitpunkt des Zugriffs auf eine Instanzvariable, ob der Variablenname am Anfang ein" _ "hat, und prüfen Sie, ob das Ziel, das versucht, auf die Variable zuzugreifen, ein Objekt ist, das diese Variable als Instanzvariable hat."

Das ist eine völlig andere mögliche Politik. Mit der ersteren Erklärung ist unklar, wo man anfangen soll. Andererseits wird in der letzteren Erklärung die Verarbeitung zum Zeitpunkt des Zugriffs auf die Variable durchgeführt, so dass sich natürlich die Idee entwickelt, nach diesem Zeitpunkt zu suchen. Tatsächlich habe ich einen Tag lang nach dem Compiler gesucht, weil ich nicht explizit verbalisiert habe, was ich zuerst tun musste. Wenn Sie später darüber nachdenken, ist es natürlich, dass Sie mit Virtual Machine spielen, aber es ist leicht, eine so natürliche Sache zu übersehen, es sei denn, Sie verbalisieren sie und organisieren Ihren Geist. Wie oft gesagt, halte ich es für eine gute Idee, die Arbeit zu beginnen, indem Sie auf leicht verständliche Weise aufschreiben, was Sie versuchen, mit dem Gefühl, es anderen zu erklären.

  1. Wichtigkeit des Verständnisses der Datenstruktur Die folgenden Wörter wurden auf der referenzierten Site aufgelistet

"The key to understanding a program is to understand its data structures. With that in hand, the algorithms usually become obvious."

Es gab viele Szenen, in denen ich mir dessen sehr bewusst war. Insbesondere um den Compiler (in dem Moment, in dem ich die Datenstruktur von AST, DFA usw. verstanden habe, wurde der gesamte Fluss sofort sichtbar), selbst bei der Implementierung privater Variablen, beispielsweise in der Datenstruktur, in der die Methode Funktionen und Instanzen enthält. Zu wissen, dass etwas und globale Variablen Wörterbücher sind, war sehr wichtig, um den gesamten Prozessablauf zu verstehen. Eine praktischere Lektion aus dieser Lektion ist, dass Sie die Header-Datei (.h-Datei) nicht unterschätzen sollten. Es ist auch einfach, mehr als nur den Arbeitscode zu sehen, indem man sich die Strukturdefinition in der Header-Datei ansieht.

Referenzierte, hilfreiche Seite

Offizieller Leitfaden für Python-Entwickler https://docs.python.org/devguide/

Ein Blog mit vielen leicht verständlichen und detaillierten Artikeln über die interne Verarbeitung von Python http://eli.thegreenplace.net/tag/python-internals

Recommended Posts

Eine Geschichte über den Versuch, private Variablen in Python zu implementieren.
Eine Geschichte darüber, wie man einen relativen Pfad in Python angibt.
Eine Geschichte über den Versuch, Linter mitten in einem Python (Flask) -Projekt vorzustellen
Eine Geschichte über den Versuch, mehrere Python-Versionen auszuführen (Mac Edition)
Ich möchte eine Variable in einen Python-String einbetten
Ich möchte Timeout einfach in Python implementieren
Ich habe versucht, einen Pseudo-Pachislot in Python zu implementieren
Eine Geschichte über das Ausprobieren eines (Golang +) Python-Monorepo mit Bazel
Eine verwirrende Geschichte mit zwei Möglichkeiten, XGBoost in Python + zu implementieren
Ich habe versucht, einen eindimensionalen Zellautomaten in Python zu implementieren
Eine Geschichte über einen Python-Anfänger, der versucht, Google-Suchergebnisse mithilfe der API abzurufen
[Django] Eine Geschichte über das Feststecken in einem Sumpf beim Versuch, einen Reißverschluss mit einem Formular zu validieren [TDD]
Eine Geschichte über den Versuch, einen Chot zu automatisieren, wenn Sie selbst kochen
Eine Geschichte über das Hinzufügen einer REST-API zu einem mit Python erstellten Daemon
So überprüfen Sie die Speichergröße einer Variablen in Python
Implementieren Sie einen deterministischen endlichen Automaten in Python, um Vielfache von 3 zu bestimmen
Ich habe versucht, ein missverstandenes Gefangenendilemma in Python zu implementieren
Wenn Sie einer Variablen in Python einen CSV-Export zuweisen möchten
Ich habe versucht, Permutation in Python zu implementieren
Ich habe versucht, PLSA in Python 2 zu implementieren
Wie bekomme ich Stacktrace in Python?
Ich habe versucht, ADALINE in Python zu implementieren
Eine Geschichte über Python Pop und Append
Eine Geschichte über alles von der Datenerfassung über die KI-Entwicklung bis hin zur Veröffentlichung von Webanwendungen in Python (3. KI-Entwicklung)
Eine Geschichte, die es aufgegeben hat, JavaScripthon unter Windows auszuführen.
Die Geschichte, den Versuch aufzugeben, mit Heroku eine Verbindung zu MySQL herzustellen
Eine Geschichte über einen Anfänger, der sich bemüht, CentOS 8 einzurichten (Verfahrensnotiz)
Ein Memorandum, weil ich beim Versuch, MeCab mit Python zu verwenden, gestolpert bin
Ich habe versucht, Trumps Kartenspiel in Python zu implementieren
Eine Geschichte, die unter einem Unterschied im Betriebssystem litt, als sie versuchte, ein Papier zu implementieren
Versuchen Sie, Oni Mai Tsuji Miserable mit Python zu implementieren
Berechnen wir das statistische Problem mit Python
So löschen Sie einen Taple in einer Liste (Python)
Ich möchte mit Python ein Fenster erstellen
So implementieren Sie Shared Memory in Python (mmap.mmap)
So erstellen Sie eine JSON-Datei in Python
Ich habe mich eingehend mit der Verarbeitung von Variablen in Python befasst
Ein Memo, das ich in Python zusammengeführt habe
Eine clevere Möglichkeit zur Zeitverarbeitung mit Python
So implementieren Sie eine Verlaufsauswahl in Houdini
Schritte zum Entwickeln einer Webanwendung in Python
Datenanalyse in Python: Ein Hinweis zu line_profiler
Ich habe versucht, TOPIC MODEL in Python zu implementieren
Eine Geschichte über das Ausführen von Python auf PHP auf Heroku
Denken Sie daran, eine Python 3-Umgebung in einer Mac-Umgebung zu erstellen
So fügen Sie Python ein Modul hinzu, das Sie in Julialang eingefügt haben
So benachrichtigen Sie Discord-Kanäle in Python
[Python] Wie zeichnet man mit Matplotlib ein Histogramm?
Eine Geschichte über den Versuch, den Testprozess eines 20 Jahre alten Systems in C zu verbessern
Ich habe versucht, eine selektive Sortierung in Python zu implementieren
Fehler beim Versuch, psycopg2 in Python zu installieren
Eine Geschichte über das Ändern von Python und das Hinzufügen von Funktionen
Eine Geschichte über einen Versuch, uwsgi auf einer fehlgeschlagenen EC2-Instanz zu installieren
Ich habe versucht, ein scheinbar Windows-Snipper-Tool mit Python zu implementieren
Implementieren Sie XENO mit Python
Private Methode in Python