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.
** 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.
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.
Erstens, wenn Sie das Problem unterteilen, an dem Sie gerade arbeiten
Ich habe mich vom letzten Mal an auf GDB konzentriert, um das herauszufinden.
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.
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.
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:
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.
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.
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.
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.
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.
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.
"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.
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