Une histoire sur la tentative d'implémentation de variables privées en Python.

Dernier synopsis

Dans le cadre d'une conférence sur l'exploration des logiciels à grande échelle, j'ai décidé d'explorer python, qui m'intéressait depuis un certain temps. Puisque la tâche de réactivation de l'instruction d'impression python2 a été accomplie, cette fois, nous aborderons la tâche d'introduire une variable privée dans python.

Contexte

Il n'y a pas de ** variable privée dans la classe Python **. Au lieu de cela, il est habituel de traiter une variable comme une variable privée en la préfixant avec un "_". C'est laissé à la conscience du programmeur. C'est une idée très différente des langages orientés objet tels que Java. L'arrière-plan est le post de débordement de pile suivant http://stackoverflow.com/questions/1641219/does-python-have-private-variables-in-classes Prière de se référer à. Cependant, je pensais qu'il pourrait y avoir un nouveau programmeur qui ne connaît pas la coutume de python et réécrit des variables qui ne devraient pas être réécrites de l'extérieur de la classe (sans permission), donc cette fois je ne me soucie pas de l'idée de python, Je voulais mettre en œuvre la convention de manière plus rigoureuse.

Définition du problème

La variable privée d'origine est conçue pour générer une erreur et arrêter le traitement lorsqu'elle est accédée de l'extérieur (probablement). Cependant, comme vous pouvez le voir en l'essayant, il semble qu'il existe différents processus pour accéder aux variables privées de l'extérieur dans le traitement interne de python, donc si vous essayez d'échouer avec une erreur, python tombera sans autorisation (ou plutôt, il ne sera pas compilé) Hmm). Par conséquent, cette fois, j'ai décidé de l'implémenter en émettant un avertissement lors de l'accès à la variable privée. Je me demande si j'ai introduit une variable privée, mais je pensais que c'était suffisant pour aider la cible des utilisateurs qui ne connaissaient pas les conventions python, alors j'ai compromis.

Fumbling

Premièrement, si vous subdivisez le problème sur lequel vous travaillez cette fois

  1. Capturez le moment de l'accès aux variables de classe
  2. Vérifiez si le nom de la variable commence par "_"
  3. Assurez-vous que la cible à laquelle vous essayez d'accéder à la variable est un objet contenant cette variable comme variable d'instance. Il peut être divisé en trois processus.

Poursuivant à partir de la dernière fois, je me suis essentiellement concentré sur gdb pour le savoir.

Capturez le moment de l'accès aux variables

Au début, je cherchais le compilateur parce que j'ai été traîné par la dernière fois que je jouais avec le compilateur. Mais si vous y réfléchissez, cette méthode ne devrait pas fonctionner. C'est parce que la classe python n'est qu'un dictionnaire après tout, et au moment de la compilation, on ne sait pas toujours quelle clé (variable membre) elle possède. Par conséquent, au moment de la compilation, vous n'accédez pas aux informations des variables membres de l'instance et vous ne pouvez savoir si les variables membres existent qu'en accédant aux variables. En d'autres termes, ce que vous devez vraiment explorer, c'est comment gérer le moment auquel une variable membre est réellement accédée en fonction du code d'opération compilé. Vous cherchez donc la machine virtuelle de python, pas le compilateur. Après quelques recherches, j'ai trouvé qu'un fichier appelé ceval.c évalue le code d'opération. Le traitement qui l'entoure est décrit en détail sur le site suivant. http://www.nasuinfo.or.jp/FreeSpace/kenji/sf/python/virtualMachine/PyVM.html

Ensuite, j'ai utilisé dis, un module intégré pratique de python, pour déterminer le code d'opération à regarder. dis convertit le code python passé en code d'opération utilisé dans l'interpréteur python, j'ai donc généré le code d'opération pour le code suivant.

Avant la conversion


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")

Après la conversion


  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

Comme je l'ai vu, j'ai trouvé que le code d'opération STORE_ATTR était suspect. Avec cela, les matériaux sont prêts pour le moment. De là, nous explorerons ceval.c en utilisant gdb.

Explorer l'accès aux variables

Tout d'abord, quand j'ai honnêtement cherché la chaîne de caractères STORE_ATTR dans ceval.c, j'ai trouvé la partie suivante.

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();
}

Il semble que nous traitons le code d'opération STORE_ATTR ici. Et il y avait certaines choses que je pouvais comprendre en regardant ce code. Tout d'abord, il peut être déduit du nom de la variable que le nom est ** le nom de la variable à laquelle vous souhaitez accéder ** et le propriétaire est l'instance ** que vous devez définir **. Et il semble que la fonction qui définit réellement la variable soit PyObject_SetAttr. Les arguments de la fonction PyObject_SetAttr sont nom, propriétaire, v, il est donc présumé que v est probablement la valeur que vous souhaitez définir. Tout d'abord, afin de confirmer ces hypothèses, j'ai défini un point d'arrêt ici pour le moment et j'ai exécuté le code suivant test.py avec gdb.

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")

Cependant, lorsque je l'essaye, il semble que python utilise beaucoup STORE_ATTR même lors du chargement de modules, et si c'est le cas, trop de traitement sera intercepté et le travail ne se poursuivra pas. Si vous ne l'arrêtez pas uniquement lorsque l'attribut "_foo" est appelé, vous ne pouvez pas le toucher. Le problème est que le nom est de type PyObject, un objet Python (cette fois unicode), donc vous ne pouvez tout simplement pas accéder au nom de la variable. J'ai donc examiné l'implémentation de type unicode de python décrite dans unicodeobject.h et unicodeobject.c pour voir si elle pouvait être convertie en une chaîne C (tableau de caractères). Ensuite, dans unicodeobject.h, il y avait une fonction comme ça.


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

En fait, lorsque j'ai appliqué cette fonction à l'objet nom à titre d'essai, j'ai pu obtenir la chaîne de caractères désignée par le nom en tant que type char *. En utilisant cela, j'ai défini un point d'arrêt à l'intérieur de l'instruction if qui tient à la condition que le nom et "_foo" correspondent afin que gdb s'arrête uniquement lorsque le nom est "_foo", et j'ai essayé d'exécuter gdb. ,Ça s'est bien passé. Ici, nous pouvons certainement prouver l'hypothèse que nom est le nom de la variable à laquelle nous essayons réellement d'accéder.

Ensuite, nous avons examiné le traitement de la fonction PyObject_SetAttr et testé l'hypothèse selon laquelle le propriétaire est l'instance que le propriétaire tente réellement de définir, et v est la valeur que le propriétaire tente de définir. Après avoir plongé pendant un certain temps, j'ai trouvé que la fonction suivante était appelée avec le propriétaire comme premier argument.


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;
}

L'important est d'obtenir le dictionnaire du premier argument (obj) ici. Puisque les instances de classe python sont essentiellement des dictionnaires, cela signifie que vous accédez à obj lui-même. En d'autres termes, nous avons pu confirmer que le propriétaire était bien l'instance à laquelle accéder. Et comme vous pouvez voir que v est défini dans le dictionnaire avec la clé nommée name avec PyDict_SetItem, vous pouvez également confirmer que v est la valeur à définir.

Donc, ce que vous devez faire à partir d'ici est de trouver les informations de ** l'instance appelante ** et de les faire correspondre avec le propriétaire (comparez le pointeur). J'ai donc commencé à chercher des informations sur l'instance appelante.

Rechercher des informations sur une instance

Pour les méthodes Python, l'instance elle-même est toujours définie sur le premier argument. En d'autres termes, vous devriez être en mesure de recalculer les informations d'instance à partir de la méthode. La méthode se référait à la documentation officielle pour savoir comment savoir à quelle instance elle appartient. https://docs.python.org/3/c-api/method.html Apparemment, une fonction appelée PyMethod_GET_SELF obtient l'instance à laquelle elle appartient. Et une autre chose que j'ai découverte est qu'une méthode est essentiellement une structure qui contient des informations sur la fonction à exécuter et l'instance à laquelle elle appartient. Cela deviendra important plus tard. Sur la base de ces informations, j'ai de nouveau exécuté test.py avec gdb pour rechercher des informations sur l'instance. En conclusion, aucune information n'a été trouvée pour l'instance appelant la méthode, quel que soit le niveau de la pile. Et quand j'ai grimpé la trace de la pile, j'ai découvert pourquoi.

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;
}

Vous pouvez voir que l'appel de méthode semble être exécuté par la fonction ci-dessus. Et si vous regardez de plus près ce processus, vous pouvez voir que les appels de méthode sont à peu près effectués dans les étapes suivantes:

  1. Une méthode est juste une combinaison d'informations de fonction et d'instance, séparez donc la fonction et l'instance.
  2. Initialisez les arguments passés à la fonction sous forme de tapples
  3. Définissez l'instance dans le premier argument du taple.
  4. Exécution de la fonction

Réfléchissez à deux fois. ** Les instances ne sont que des arguments de fonction. ** Cela signifie qu'au moment où la méthode est exécutée par method_call, les informations sur l'instance à laquelle appartient la méthode sont déjà perdues. Et le processus STORE_ATTR est exécuté après l'appel de method_call. En d'autres termes, si cela est laissé tel quel, il ne sera pas possible de faire la distinction entre l'accès par des fonctions externes telles que les suivantes et l'accès par des méthodes internes au moment de l'accès aux variables membres de l'instance.

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)

C'est un problème. D'une manière ou d'une autre, nous devons transmettre les informations d'instance de l'appelant au processus qui exécute STORE_ATTR. Si cela se produit, vous n'avez pas d'autre choix que de le remettre de force.

Transmission des informations sur l'instance

La méthode que j'ai utilisée cette fois est d'enregistrer de force l'instance dans la variable globale avec le nom de variable "\ _ \ _ instance_parente \ _ \ _" au moment de method_call. En fait, je devrais le définir avec une nouvelle portée, mais c'est gênant, alors j'ai décidé de percer avec une telle méthode de force brute cette fois. Les variables globales sont l'une des propriétés d'une fonction et sont implémentées en tant que dictionnaire python. Lors de la vérification de l'implémentation du type de dictionnaire dans dictobject.h et dictobject.c, je l'ai enregistré dans la variable globale comme suit.

Objet de classe modifié.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;
}

La partie entourée par #if PRIVATE_ATTRIBUTE et # endif est le code ajouté. À partir de la fonction, vous pouvez voir que le dictionnaire dans lequel les variables globales sont enregistrées est obtenu et que les variables y sont enregistrées de force. Un point à noter est que si vous ne supprimez pas \ _ \ _ instance_parente \ _ \ _ des variables globales lorsque la méthode finit de s'exécuter, vous pouvez y accéder une fois que la variable privée est accédée à partir de la méthode interne. Il est devenu. Par conséquent, lorsque la méthode finit de s'exécuter, PyDict_DelItem supprime finalement \ _ \ _ instance_parente \ _ \ _.

Ensuite, lorsque STORE_ATTR a été exécuté, le traitement suivant a été ajouté et corrigé.

Ceval modifié.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();
        }

Si le premier caractère du nom est \ _, il est déterminé que la cible d'accès est une variable privée. Et, seulement s'il y a \ _ \ _ instance_parente \ _ \ _ dans la variable globale et que le propriétaire et \ _ \ _ l'instance_parente \ _ \ _ correspondent, il est considéré comme un accès légitime, sinon un avertissement est affiché. J'ai ajouté un processus simple d'affichage. Et quand j'essaye de compiler avec PRIVATE_ATTRIBUTE mis à 1 dans la configuration ...

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

Le constructeur est appelé sur la ligne «f = Foo (1)». À première vue, il semble que l'avertissement n'apparaisse pas ici et l'avertissement se produit lors d'un accès de l'extérieur. Mais voici le problème.

python


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

Pour une raison quelconque, j'étais en colère même si c'était un accès de l'intérieur. Lorsque j'exécute test.py sur gdb pour trouver la cause, \ _ \ _ instance_parente \ _ \ _ n'est pas enregistrée dans la variable globale. Quelque part, le processus ne fonctionne pas. En regardant la trace de la pile, il s'est avéré que ** method_call n'était pas appelé ** dans f.mymethod (3). Au lieu de cela, une mystérieuse fonction appelée fast_function a été appelée.

Combattre avec fast_function

Quand j'ai recherché ce qu'était fast_function, il y avait une explication utile sur le site suivant. 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.

Apparemment, il est inutile de créer un tas d'arguments un par un, il semble donc que python essaie d'accélérer en récupérant les variables de la pile. La fonction appelée fast_function est en charge de cela. En d'autres termes, il est plus rapide en ne passant pas par method_call. Cependant, la méthode doit toujours être appelée à un moment donné et l'instance doit avoir été acquise. Si vous l'enregistrez dans la variable globale à ce moment, vous devriez être en mesure de réaliser la même chose qu'une extension de l'implémentation précédente. Ainsi, lorsque j'ai recherché la partie qui accède à l'instance à laquelle appartient la méthode, c'est-à-dire la partie à laquelle PyMethod_GET_SELF est appelée, la partie suivante de la fonction call_function de ceval.c a été touchée.

call_à l'intérieur de la fonction fonction


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++;
}

Donc, avec une idée simple, je l'ai réécrit comme suit.

Appel modifié_fonction fonction


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
}

Notez que temp et funcdict sont les mêmes que lorsque classobject.c a été modifié, y compris le type et la signification. Si vous ne le définissez pas au début de la fonction, le compilateur se mettra en colère, donc la définition n'est tout simplement pas dans ce code. La suppression a été effectuée comme suit à la fin de la fonction.


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;
}

Maintenant, essayez de le compiler et de l'exécuter. Ensuite, sur Mac OS, cela a fonctionné comme suit.

>>> 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!

Vous pouvez voir que mymethod ne génère aucune erreur. C'était étonnamment facile. Cependant, lorsque j'ai essayé de compiler la source avec les mêmes modifications sur Ubuntu, j'étais en colère de ne pas jouer avec la variable Global, et la compilation a échoué avec une erreur. À l'origine, il devrait être modifié pour qu'il fonctionne sur Ubuntu, mais cette fois je n'ai pas eu beaucoup de temps, alors j'ai fait un compromis pour le moment.

Ce que j'ai appris

Grâce à cette expérience, j'ai appris des leçons importantes pour jouer avec un logiciel à grande échelle, donc je vais le résumer verbalement pour moi-même.

  1. Vous devez vous fier à la documentation officielle Cette fois, il y avait de nombreuses scènes où la compréhension progressait d'un seul coup rien qu'en lisant le document officiel. Bien sûr, ce n'est pas le cas si la documentation officielle n'est pas en place, mais je pense que c'est une bonne idée de lire la documentation officielle dès que possible. C'est une évidence. Cependant, il est difficile de trouver des documents liés au traitement interne de nombreux logiciels, pas seulement python, par simple recherche. Le meilleur moyen de trouver la documentation officielle est d'utiliser les mots clés contributeur et développeur. Si vous recherchez un logiciel à grande échelle, recherchez «contributeur de nom de logiciel», «guide du développeur de nom de logiciel», etc., et trouvez un guide pour la partie que vous essayez de contribuer (grammaire pour la grammaire, performances pour la performance, etc.). Je pense que c'est une bonne idée de commencer par la chercher. Par conséquent, l'efficacité est complètement différente entre suivre le code après avoir créé une étoile et simplement le suivre.

  2. L'importance de verbaliser le processus Je pense qu'en exprimant clairement ce que vous voulez faire, il devient clair ce que vous devez faire pour la première fois. Par exemple, la politique d'implémentation de variables privées,

"Rendre les variables d'une instance accessibles uniquement à partir de cette instance"

je veux dire

"Au moment de l'accès à une variable d'instance, déterminez si le nom de la variable a un" _ "au début et vérifiez si la cible essayant d'accéder à la variable est un objet qui a cette variable comme variable d'instance."

C'est une politique possible complètement différente. Avec la première explication, on ne sait pas par où commencer. D'autre part, dans cette dernière explication, le traitement est effectué au moment de l'accès à la variable, de sorte qu'il se développe naturellement dans l'idée de trouver ce timing. En fait, j'ai passé une journée à chercher le compilateur parce que je n'ai pas explicitement verbalisé ce que je devais faire en premier. Quand vous y réfléchissez plus tard, il est naturel que vous jouiez avec Virtual Machine, mais il est facile d'oublier une chose aussi naturelle à moins que vous ne la verbalisiez et n'organisiez votre esprit. Comme on le dit souvent, je pense que c'est une bonne idée de commencer le travail en écrivant ce que vous essayez de faire d'une manière facile à comprendre avec le sentiment de l'expliquer aux autres.

  1. Importance de comprendre la structure des données Les mots suivants ont été répertoriés sur le site référencé

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

Il y avait de nombreuses scènes où j'en étais parfaitement conscient. Surtout autour du compilateur (au moment où j'ai compris la structure de données d'AST, DFA, etc., tout le flux est devenu visible à la fois), même dans l'implémentation de variables privées, par exemple, dans la structure de données où la méthode contient des fonctions et des instances Savoir que quelque chose et que les variables globales sont des dictionnaires était très important pour comprendre le flux global du processus. Une leçon plus pratique de cette leçon est que vous ne devez pas sous-estimer le fichier d'en-tête (fichier .h). Il est également facile de voir plus que simplement regarder le code de travail en regardant la définition de la structure dans le fichier d'en-tête.

Site référencé et utile

Guide officiel pour les développeurs Python https://docs.python.org/devguide/

Un blog avec de nombreux articles faciles à comprendre et détaillés sur le traitement interne de Python http://eli.thegreenplace.net/tag/python-internals

Recommended Posts

Une histoire sur la tentative d'implémentation de variables privées en Python.
Une histoire sur la façon de spécifier un chemin relatif en python.
Une histoire sur la tentative d'introduire Linter au milieu d'un projet Python (Flask)
Une histoire sur la tentative d'exécuter plusieurs versions de Python (édition Mac)
Je souhaite intégrer une variable dans une chaîne Python
Je veux facilement implémenter le délai d'expiration en python
J'ai essayé d'implémenter un pseudo pachislot en Python
Une histoire d'essayer un monorepo (Golang +) Python avec Bazel
Une histoire déroutante avec deux façons d'implémenter XGBoost en Python + notes générales
J'ai essayé d'implémenter un automate cellulaire unidimensionnel en Python
Une histoire sur un débutant Python essayant d'obtenir des résultats de recherche Google à l'aide de l'API
[Django] Une histoire sur le fait de rester coincé dans un marais en essayant de valider un zip avec un formulaire [TDD]
Une histoire d'essayer d'automatiser un chot lorsque vous cuisinez vous-même
Une histoire sur l'ajout d'une API REST à un démon créé avec Python
Comment vérifier la taille de la mémoire d'une variable en Python
Implémenter un automate fini déterministe en Python pour déterminer des multiples de 3
J'ai essayé de mettre en œuvre un jeu de dilemme de prisonnier mal compris en Python
Si vous souhaitez affecter une exportation csv à une variable en python
J'ai essayé d'implémenter la permutation en Python
J'ai essayé d'implémenter PLSA dans Python 2
Comment obtenir stacktrace en python
J'ai essayé d'implémenter ADALINE en Python
Une histoire sur Python pop and append
Une histoire sur tout, de la collecte de données au développement d'IA et à la publication d'applications Web en Python (3. développement d'IA)
Une histoire d'essayer d'exécuter JavaScripthon sur Windows et d'abandonner.
L'histoire de l'abandon d'essayer de se connecter à MySQL en utilisant Heroku
Une histoire sur un débutant essayant de configurer CentOS 8 (mémo de procédure)
Un mémorandum parce que j'ai trébuché en essayant d'utiliser MeCab avec Python
J'ai essayé d'implémenter le jeu de cartes de Trump en Python
Une histoire qui a souffert d'une différence de système d'exploitation lors de la tentative d'implémentation d'un article
Essayez d'implémenter Oni Mai Tsuji Miserable avec python
Calculons en fait le problème statistique avec Python
Comment effacer un taple dans une liste (Python)
Je veux créer une fenêtre avec Python
Comment implémenter la mémoire partagée en Python (mmap.mmap)
Comment créer un fichier JSON en Python
J'ai étudié en détail le traitement des variables en python
Un mémo que j'ai écrit un tri de fusion en Python
Une manière intelligente de chronométrer le traitement avec Python
Comment implémenter un sélecteur de dégradé dans Houdini
Étapes pour développer une application Web en Python
Analyse de données en Python: une note sur line_profiler
J'ai essayé d'implémenter TOPIC MODEL en Python
Une histoire sur l'exécution de Python sur PHP sur Heroku
Pensez à créer un environnement Python 3 dans un environnement Mac
Pour ajouter un module à python que vous mettez dans Julialang
Comment notifier les canaux Discord en Python
[Python] Comment dessiner un histogramme avec Matplotlib
Une histoire d'essayer d'améliorer le processus de test d'un système vieux de 20 ans écrit en C
J'ai essayé d'implémenter le tri sélectif en python
Erreur lors de la tentative d'installation de psycopg2 en Python
Une histoire sur la modification de Python et l'ajout de fonctions
Une histoire d'essayer d'installer uwsgi sur une instance EC2 et d'échouer
J'ai essayé d'implémenter ce qui semble être un outil de snipper Windows avec Python
Mettre en œuvre des recommandations en Python
Implémenter XENO avec python
Méthode privée en python