À quoi ça sert? – Véritable Python

By | mai 29, 2019

python pour débutant

Si vous avez déjà travaillé avec des langages de niveau inférieur tels que C ou C ++, vous avez probablement entendu parler de pointeurs. Les pointeurs vous permettent de créer une grande efficacité dans certaines parties de votre code. Ils sont également source de confusion pour les débutants et peuvent entraîner divers problèmes de gestion de la mémoire, même pour les experts. Alors, où sont-ils en Python, et comment pouvez-vous simuler des pointeurs en Python?

Les pointeurs sont largement utilisés en C et C ++. Il s’agit essentiellement de variables contenant l’adresse mémoire d’une autre variable. Pour un rappel sur les pointeurs, vous pouvez consulter cet aperçu sur les pointeurs C.

Dans cet article, vous comprendrez mieux le modèle d’objet de Python et découvrirez pourquoi les pointeurs en Python n’existent pas vraiment. Dans les cas où vous devez imiter le comportement d'un pointeur, vous apprendrez comment simuler des pointeurs en Python sans le cauchemar de la gestion de la mémoire.

Dans cet article, vous allez:

  • Découvrez pourquoi les pointeurs en Python n'existent pas
  • Explorez la différence entre les variables C et les noms Python
  • Simuler des pointeurs en Python
  • Expérimentez avec de vrais pointeurs en utilisant types

Pourquoi Python n’a-t-il pas de pointeurs?

La vérité est que je ne sais pas. Les pointeurs en Python pourraient-ils exister nativement? Probablement, mais les pointeurs semblent aller à l'encontre du Zen de Python. Les pointeurs encouragent les changements implicites plutôt qu'explicites. Souvent, ils sont complexes au lieu de simples, surtout pour les débutants. Pire encore, ils demandent des moyens de se tirer une balle dans le pied ou font quelque chose de très dangereux, comme lire dans une partie de la mémoire que vous n’étiez pas supposée faire.

Python a tendance à essayer d’abstraire les détails d’implémentation tels que les adresses mémoire de ses utilisateurs. Python se concentre souvent sur la convivialité plutôt que sur la vitesse. Par conséquent, les pointeurs en Python n’ont pas vraiment de sens. N'ayez crainte, Python vous offre, par défaut, certains des avantages de l'utilisation de pointeurs.

Comprendre les pointeurs en Python nécessite un bref détour par les détails d’implémentation de Python. Plus précisément, vous devez comprendre:

  1. Objets immuables vs mutables
  2. Variables / Noms Python

Conservez vos adresses de mémoire et commençons.

Objets en Python

En Python, tout est un objet. Pour preuve, vous pouvez ouvrir une REPL et explorer en utilisant isinstance ():

>>>

>>> isinstance(1, objet)
Vrai
>>> isinstance(liste(), objet)
Vrai
>>> isinstance(Vrai, objet)
Vrai
>>> def foo():
...     passer
...
>>> isinstance(foo, objet)
Vrai

Ce code vous montre que tout dans Python est bien un objet. Chaque objet contient au moins trois données:

  • Nombre de références
  • Type
  • Valeur

Le compte de référence est pour la gestion de la mémoire. Pour un examen approfondi des éléments internes de la gestion de la mémoire en Python, vous pouvez lire Gestion de la mémoire en Python.

Le type est utilisé au niveau de la couche CPython pour assurer la sécurité du type pendant l'exécution. Enfin, il y a la valeur, qui est la valeur réelle associée à l’objet.

Cependant, tous les objets ne sont pas identiques. Il y a une autre distinction importante que vous devrez comprendre: les objets immuables ou mutables. Comprendre la différence entre les types d'objets aide vraiment à clarifier la première couche de l'oignon qui est des pointeurs en Python.

Objets immuables ou mutables

En Python, il existe deux types d'objets:

  1. Objets immuables ne peut pas être changé.
  2. Objets mutables peut être changé.

Comprendre cette différence est la première clé pour naviguer dans le paysage des pointeurs en Python. Voici une ventilation des types courants et de leur caractère mutable ou immuable:

Type Immuable?
int Oui
flotte Oui
bool Oui
complexe Oui
tuple Oui
Frozenset Oui
str Oui
liste Non
ensemble Non
dict Non

Comme vous pouvez le constater, beaucoup de types primitifs couramment utilisés sont immuables. Vous pouvez le prouver vous-même en écrivant du Python. Vous aurez besoin de quelques outils de la bibliothèque standard Python:

  1. id () renvoie l'adresse mémoire de l'objet.
  2. est résultats Vrai si et seulement si deux objets ont la même adresse mémoire.

Une fois encore, vous pouvez les utiliser dans un environnement REPL:

>>>

>>> X = 5
>>> identifiant(X)
94529957049376

Dans le code ci-dessus, vous avez attribué la valeur 5 à X. Si vous essayez de modifier cette valeur avec addition, vous obtiendrez un nouvel objet:

>>>

>>> X + = 1
>>> X
6
>>> identifiant(X)
94529957049408

Même si le code ci-dessus semble modifier la valeur de X, vous obtenez un Nouveau objet comme une réponse.

le str le type est aussi immuable:

>>>

>>> s = "real_python"
>>> identifiant(s)
140637819584048
>>> s + = "_roches"
>>> s
'real_python_rocks'
>>> identifiant(s)
140637819609424

Encore, s se termine avec un différent adresses de mémoire après la + = opération.

Essayer de muter directement la chaîne s entraîne une erreur:

>>>

>>> s[[[[0] = "R"
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
Erreur-type: L'objet 'str' ne prend pas en charge l'affectation d'élément

Le code ci-dessus échoue et Python indique que str ne prend pas en charge cette mutation, ce qui est conforme à la définition du str le type est immuable.

Comparez cela avec un objet modifiable, comme liste:

>>>

>>> ma liste = [[[[1, 2, 3]
>>> identifiant(ma liste)
140637819575368
>>> ma liste.ajouter(4)
>>> ma liste
[1, 2, 3, 4]
>>> identifiant(ma liste)
140637819575368

Ce code montre une différence majeure entre les deux types d'objets. ma liste a un identifiant à l'origine. Même après 4 est ajouté à la liste, ma liste a la même id. C'est parce que le liste le type est mutable.

Une autre façon de démontrer que la liste est modifiable consiste à assigner:

>>>

>>> ma liste[[[[0] = 0
>>> ma liste
[0, 2, 3, 4]
>>> identifiant(ma liste)
140637819575368

Dans ce code, vous mutez ma liste et mettre son premier élément à 0. Cependant, il conserve le même identifiant même après cette affectation. Avec des objets mutables et immuables à l’écart, la prochaine étape de votre voyage vers l’illumination de Python consiste à comprendre l’écosystème variable de Python.

Comprendre les variables

Les variables Python diffèrent fondamentalement des variables C ou C ++. En fait, Python n’a même pas de variables. Python a des noms, pas des variables.

Cela peut sembler pédant, et pour l’essentiel, c’est le cas. La plupart du temps, il est parfaitement acceptable de considérer les noms Python comme des variables, mais il est important de comprendre la différence. Cela est particulièrement vrai lorsque vous naviguez dans le sujet complexe des pointeurs en Python.

Pour vous aider à comprendre la différence, vous pouvez examiner le fonctionnement des variables en C, leur signification, puis les comparer à la manière dont les noms fonctionnent en Python.

Variables en C

Disons que vous aviez le code suivant qui définit la variable X:

Cette ligne de code comporte plusieurs étapes distinctes lorsqu’elle est exécutée:

  1. Allouez assez de mémoire pour un entier
  2. Attribuer la valeur 2337 à cet emplacement de mémoire
  3. Indique que X pointe vers cette valeur

Dans une vue simplifiée de la mémoire, cela pourrait ressembler à ceci:

Représentation en mémoire de X (2337)

Ici, vous pouvez voir que la variable X a un faux emplacement mémoire de 0x7f1 et la valeur 2337. Si, plus tard dans le programme, vous souhaitez modifier la valeur de X, vous pouvez faire ce qui suit:

Le code ci-dessus attribue une nouvelle valeur (2338) à la variable X, ainsi écrasement la valeur précédente. Cela signifie que la variable X est mutable. La mise en page de la mémoire mise à jour affiche la nouvelle valeur:

Nouvelle représentation en mémoire de X (2338)

Notez que l'emplacement de X n'a pas changé, juste la valeur elle-même. C'est un point important. Cela signifie que X est l'emplacement de la mémoire, pas seulement un nom pour cela.

Une autre façon de penser à ce concept est en termes de propriété. Dans un sens, X possède l'emplacement de la mémoire. X est, dans un premier temps, une boîte vide pouvant contenir exactement un entier dans lequel des valeurs entières peuvent être stockées.

Lorsque vous attribuez une valeur à X, vous placez une valeur dans la case qui X possède. Si vous voulez introduire une nouvelle variable (y), vous pouvez ajouter cette ligne de code:

Ce code crée un Nouveau boîte appelée y et copie la valeur de X dans la boîte. Maintenant, la disposition de la mémoire ressemblera à ceci:

Représentation en mémoire de X (2338) et de Y (2338)

Remarquez le nouvel emplacement 0x7f5 de y. Même si la valeur de X a été copié à y, la variable y possède une nouvelle adresse en mémoire. Par conséquent, vous pouvez écraser la valeur de y sans affecter X:

Maintenant, la disposition de la mémoire ressemblera à ceci:

Représentation mise à jour de Y (2339)

Encore une fois, vous avez modifié la valeur à y, mais ne pas son emplacement. De plus, vous n'avez pas affecté l'original X variable du tout. Cela contraste fortement avec le fonctionnement des noms Python.

Noms en Python

Python n'a pas de variables. Il a des noms. Oui, c’est un point pédant et vous pouvez certainement utiliser le terme variables autant que vous le souhaitez. Il est important de savoir qu'il existe une différence entre les variables et les noms.

Prenons le code équivalent de l’exemple C ci-dessus et écrivons-le en Python:

Tout comme en C, le code ci-dessus est décomposé en plusieurs étapes distinctes lors de l'exécution:

  1. Créer un PyObject
  2. Définissez le type de code sur entier pour le PyObject
  3. Définissez la valeur sur 2337 pour le PyObject
  4. Créez un nom appelé X
  5. Point X à la nouvelle PyObject
  6. Augmenter le nombre de PyObject par 1

En mémoire, cela pourrait ressembler à quelque chose comme ça:

Représentation Python en mémoire de X (2337)

Vous pouvez voir que la disposition de la mémoire est très différente de la disposition C d’avant. Au lieu de X posséder le bloc de mémoire où la valeur 2337 réside, l’objet Python nouvellement créé possède la mémoire où 2337 vies. Le nom python X ne possède pas directement tout adresse de mémoire dans la façon dont la variable C X possédait un emplacement statique en mémoire.

Si vous essayez d’attribuer une nouvelle valeur à X, vous pouvez essayer ce qui suit:

Ce qui se passe ici est différent de l’équivalent C, mais pas trop de la liaison originale en Python.

Ce code:

  • Crée un nouveau PyObject
  • Définit le type de code sur entier pour le PyObject
  • Définit la valeur sur 2 pour le PyObject
  • Points X à la nouvelle PyObject
  • Augmente le compte de la nouvelle PyObject par 1
  • Diminue le nombre de personnes âgées PyObject par 1

Maintenant, en mémoire, cela ressemblerait à quelque chose comme ça:

Nom Python indiquant un nouvel objet (2338)

Ce diagramme aide à illustrer que X pointe vers une référence à un objet et ne possède pas l’espace mémoire comme auparavant. Cela montre aussi que le x = 2338 commande n'est pas une assignation, mais lie le nom X à une référence.

En outre, l’objet précédent (qui tenait le 2337 valeur) est maintenant en mémoire avec un nombre de réf de 0 et sera nettoyé par le ramasse-miettes.

Vous pouvez introduire un nouveau nom, y, au mix comme dans l'exemple C:

En mémoire, vous auriez un nouveau nom, mais pas nécessairement un nouvel objet:

Noms X et Y pointant vers 2338

Maintenant, vous pouvez voir qu'un nouvel objet Python a ne pas été créé, juste un nouveau nom qui pointe vers le même objet. De plus, le nombre de références de l’objet a augmenté de un. Vous pouvez vérifier l’égalité d’identité d’objet pour confirmer qu’elles sont identiques:

Le code ci-dessus indique que X et y sont le même objet. Ne vous y trompez pas: y est toujours immuable.

Par exemple, vous pouvez effectuer des ajouts sur y:

>>>

>>> y + = 1
>>> y est X
Faux

Après l'appel d'addition, vous êtes renvoyé avec un nouvel objet Python. Maintenant, la mémoire ressemble à ceci:

x nom et y nomme différents objets

Un nouvel objet a été créé et y pointe maintenant vers le nouvel objet. Fait intéressant, c'est le même état final si vous aviez lié y à 2339 directement:

L'instruction ci-dessus entraîne le même état de mémoire finale que l'ajout. Pour récapituler, en Python, vous n’affectez pas de variables. Au lieu de cela, vous liez des noms à des références.

Note sur les objets internes en Python

Maintenant que vous comprenez comment les objets Python sont créés et les noms liés à ces objets, il est temps de jeter une clé dans la machine. Cette clé porte le nom d'objets internés.

Supposons que vous ayez le code Python suivant:

>>>

>>> X = 1000
>>> y = 1000
>>> X est y
Vrai

Comme ci-dessus, X et y sont les deux noms qui pointent sur le même objet Python. Mais l'objet Python qui contient la valeur 1000 n’est pas toujours garanti d’avoir la même adresse mémoire. Par exemple, si vous deviez ajouter deux nombres ensemble pour obtenir 1000, vous vous retrouveriez avec une adresse mémoire différente:

>>>

>>> X = 1000
>>> y = 499 + 501
>>> X est y
Faux

Cette fois, la ligne x est y résultats Faux. Si cela prête à confusion, ne vous inquiétez pas. Voici les étapes qui se produisent lorsque ce code est exécuté:

  1. Créer un objet Python (1000)
  2. Attribuer le nom X à cet objet
  3. Créer un objet Python (499)
  4. Créer un objet Python (501)
  5. Ajouter ces deux objets ensemble
  6. Créer un nouvel objet Python (1000)
  7. Attribuer le nom y à cet objet

N'est-ce pas inutile? Oui, c’est vrai, mais c’est le prix que vous payez pour bénéficier de tous les avantages de Python. Vous n'avez jamais à vous soucier de nettoyer ces objets intermédiaires ni même à savoir qu'ils existent! La joie est que ces opérations sont relativement rapides, et vous n'avez jamais eu à connaître aucun de ces détails jusqu'à présent.

Les développeurs Python de base, dans leur sagesse, ont également remarqué ce gaspillage et ont décidé de faire quelques optimisations. Ces optimisations donnent lieu à des comportements qui peuvent surprendre les nouveaux arrivants:

>>>

>>> X = 20
>>> y = 19 + 1
>>> X est y
Vrai

Dans cet exemple, vous voyez presque le même code que précédemment, sauf que cette fois, le résultat est Vrai. C'est le résultat des objets internés. Python crée au préalable un certain sous-ensemble d'objets en mémoire et les conserve dans l'espace de noms global pour une utilisation quotidienne.

Quels objets dépendent de l'implémentation de Python. CPython 3.7 fait le stage suivant:

  1. Nombre entier entre -5 et 256
  2. Chaînes contenant uniquement des lettres ASCII, des chiffres ou des traits de soulignement

Le raisonnement derrière cela est que ces variables sont extrêmement susceptibles d'être utilisées dans de nombreux programmes. En internant ces objets, Python empêche les appels d’allocation de mémoire aux objets utilisés systématiquement.

Les chaînes de moins de 20 caractères contenant des lettres ASCII, des chiffres ou des traits de soulignement seront internées. Le raisonnement derrière cela est que ces personnes sont supposées être une sorte d'identité:

>>>

>>> s1 = "realpython"
>>> identifiant(s1)
140696485006960
>>> s2 = "realpython"
>>> identifiant(s2)
140696485006960
>>> s1 est s2
Vrai

Ici vous pouvez voir que s1 et s2 les deux pointent vers la même adresse en mémoire. Si vous introduisiez une lettre, un chiffre ou un trait de soulignement non-ASCII, vous obtiendriez un résultat différent:

>>>

>>> s1 = "Véritable Python!"
>>> s2 = "Véritable Python!"
>>> s1 est s2
Faux

Parce que cet exemple a un point d'exclamation (!) en elle, ces chaînes ne sont pas internées et sont des objets différents en mémoire.

Les objets internés sont souvent source de confusion. Si vous avez des doutes, rappelez-vous que vous pouvez toujours utiliser id () et est pour déterminer l'égalité d'objet.

Simuler des pointeurs en Python

Le fait que les pointeurs dans Python n’existent pas en natif ne signifie pas que vous ne pouvez pas obtenir les avantages de leur utilisation. En fait, il existe de nombreuses façons de simuler des pointeurs en Python. Vous en apprendrez deux dans cette section:

  1. Utiliser des types mutables comme pointeurs
  2. Utiliser des objets Python personnalisés

Ok, passons au fait.

Utilisation de types mutables comme pointeurs

Vous avez déjà appris sur les types mutables. Étant donné que ces objets sont modifiables, vous pouvez les traiter comme s'il s'agissait de pointeurs simulant le comportement du pointeur. Supposons que vous vouliez reproduire le code c suivant:

vide ajoute un(int *X) 
    *X + = 1;

Ce code prend un pointeur sur un entier (*X) puis incrémente la valeur de un. Voici une fonction principale pour exercer le code:

#comprendre 

int principale(vide) 
    int y = 2337;
    printf("y =% d n", y)
    ajoute un(Ety)
    printf("y =% d n", y)
    revenir 0;

Dans le code ci-dessus, vous affectez 2337 à y, affichez la valeur actuelle, incrémentez la valeur de un, puis imprimez la valeur modifiée. La sortie de l’exécution de ce code serait la suivante:

Une façon de répliquer ce type de comportement en Python consiste à utiliser un type mutable. Pensez à utiliser une liste et à modifier le premier élément:

>>>

>>> def ajoute un(X):
...     X[[[[0] + = 1
...
>>> y = [[[[2337]
>>> ajoute un(y)
>>> y[[[[0]
2338

Ici, add_one (x) accède au premier élément et incrémente sa valeur de un. Utilisant un liste signifie que le résultat final semble avoir modifié la valeur. Il existe donc des pointeurs en Python? Et bien non. Ceci n’est possible que parce que liste est un type mutable. Si vous avez essayé d'utiliser un tuple, vous obtiendrez une erreur:

>>>

>>> z = (2337,)
>>> ajoute un(z)
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
  
  
  
  Fichier "", ligne 2, dans ajoute un
Erreur-type: L'objet 'tuple' ne supporte pas l'attribution d'élément

Le code ci-dessus montre que tuple est immuable. Par conséquent, il ne prend pas en charge l'affectation d'éléments. liste n'est pas le seul type mutable. Une autre approche courante pour imiter les pointeurs en Python consiste à utiliser un dict.

Supposons que vous disposiez d’une application vous permettant de garder une trace de chaque événement intéressant. Une façon d'y parvenir serait de créer un dict et utilisez l'un des objets comme compteur:

>>>

>>> des comptoirs = "func_calls": 0
>>> def bar():
...     des comptoirs[[[["func_calls"] + = 1
...
>>> def foo():
...     des comptoirs[[[["func_calls"] + = 1
...     bar()
...
>>> foo()
>>> des comptoirs[[[["func_calls"]
2

Dans cet exemple, le des comptoirs Le dictionnaire est utilisé pour garder une trace du nombre d’appels de fonction. Après avoir appelé foo (), le compteur a augmenté à 2 comme prévu. Tous parce que dict est mutable.

Gardez à l'esprit, ce n'est que simule comportement du pointeur et ne mappe pas directement aux vrais pointeurs en C ou C ++. C'est-à-dire que ces opérations sont plus coûteuses qu'elles ne le seraient en C ou C ++.

Utiliser des objets Python

le dict Cette option est un excellent moyen d’émuler des pointeurs en Python, mais il est parfois fastidieux de se souvenir du nom de clé que vous avez utilisé. Cela est particulièrement vrai si vous utilisez le dictionnaire dans différentes parties de votre application. C’est là qu’une classe Python personnalisée peut vraiment aider.

Pour construire sur le dernier exemple, supposons que vous souhaitiez suivre les métriques dans votre application. Créer une classe est un excellent moyen d’abstraire les détails embêtants:

classe Métrique(objet):
    def __init__(soi):
        soi._métrique = 
            "func_calls": 0,
            "cat_pictures_served": 0,
        

Ce code définit un Métrique classe. Cette classe utilise toujours un dict pour conserver les données réelles, qui se trouvent dans la _métrique variable membre. Cela vous donnera la mutabilité dont vous avez besoin. Vous devez maintenant pouvoir accéder à ces valeurs. Une bonne façon de faire est d'utiliser les propriétés:

classe Métrique(objet):
    # ...

    @propriété
    def func_calls(soi):
        revenir soi._métrique[[[["func_calls"]

    @propriété
    def cat_pictures_served(soi):
        revenir soi._métrique[[[["cat_pictures_served"]

Ce code utilise @propriété. Si vous n’êtes pas familier avec les décorateurs, vous pouvez consulter cet article sur les décorateurs en python. le @propriété décorateur ici vous permet d'accéder func_calls et cat_pictures_served comme s'ils étaient des attributs:

>>>

>>> métrique = Métrique()
>>> métrique.func_calls
0
>>> métrique.cat_pictures_served
0

Le fait que vous puissiez accéder à ces noms en tant qu’attributs signifie que vous avez résumé le fait que ces valeurs sont dans un dict. Vous expliquez également plus clairement les noms des attributs. Bien sûr, vous devez pouvoir incrémenter ces valeurs:

classe Métrique(objet):
    # ...

    def inc_func_calls(soi):
        soi._métrique[[[["func_calls"] + = 1

    def inc_cat_pics(soi):
        soi._métrique[[[["cat_pictures_served"] + = 1

Vous avez introduit deux nouvelles méthodes:

  1. inc_func_calls ()
  2. inc_cat_pics ()

Ces méthodes modifient les valeurs dans les métriques dict. Vous avez maintenant une classe que vous modifiez comme si vous modifiiez un pointeur:

>>>

>>> métrique = Métrique()
>>> métrique.inc_func_calls()
>>> métrique.inc_func_calls()
>>> métrique.func_calls
2

Ici, vous pouvez accéder func_calls et appeler inc_func_calls () à différents endroits de vos applications et simulez des pointeurs en Python. Ceci est utile lorsque vous avez quelque chose comme des métriques qui doivent être utilisées et mises à jour fréquemment dans diverses parties de vos applications.

Voici la source complète pour le Métrique classe:

classe Métrique(objet):
    def __init__(soi):
        soi._métrique = 
            "func_calls": 0,
            "cat_pictures_served": 0,
        

    @propriété
    def func_calls(soi):
        revenir soi._métrique[[[["func_calls"]

    @propriété
    def cat_pictures_served(soi):
        revenir soi._métrique[[[["cat_pictures_served"]

    def inc_func_calls(soi):
        soi._métrique[[[["func_calls"] + = 1

    def inc_cat_pics(soi):
        soi._métrique[[[["cat_pictures_served"] + = 1

De vrais pointeurs avec types

D'accord, alors peut-être qu'il y a des pointeurs en Python, en particulier CPython. Utilisation de la fonction intégrée types module, vous pouvez créer de véritables pointeurs de style C en Python. Si vous ne connaissez pas types, alors vous pouvez jeter un oeil à Extending Python With C Libraries et au module “ctypes”.

La vraie raison pour laquelle vous l'utiliseriez est si vous deviez appeler une fonction dans une bibliothèque C qui nécessite un pointeur. Retournons au ajoute un() Fonction C d'avant:

vide ajoute un(int *X) 
    *X + = 1;

Là encore, ce code incrémente la valeur de X par un. Pour l'utiliser, compilez-le d'abord dans un objet partagé. En supposant que le fichier ci-dessus est stocké dans add.c, vous pouvez accomplir cela avec gcc:

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

La première commande compile le fichier source C en un objet appelé ajouter.o. La deuxième commande prend ce fichier objet non lié et produit un objet partagé appelé libadd1.so.

libadd1.so devrait être dans votre répertoire actuel. Vous pouvez le charger dans Python en utilisant types:

>>>

>>> importation types
>>> add_lib = types.CDLL("./libadd1.so")
>>> add_lib.ajoute un
<_FuncPtr object at 0x7f9f3b8852a0>

le ctypes.CDLL le code retourne un objet qui représente le libadd1 objet partagé. Parce que vous avez défini ajoute un() dans cet objet partagé, vous pouvez y accéder comme s'il s'agissait de tout autre objet Python. Avant d'appeler la fonction, vous devez spécifier la signature de la fonction. Cela aide Python à vous assurer que vous passez le bon type à la fonction.

Dans ce cas, la signature de la fonction est un pointeur sur un entier. types vous permettra de spécifier ceci en utilisant le code suivant:

>>>

>>> ajoute un = add_lib.ajoute un
>>> ajoute un.types d'arguments = [[[[types.AIGUILLE(types.c_int)]

Dans ce code, vous définissez la signature de la fonction pour correspondre à ce que C attend. Maintenant, si vous essayiez d'appeler ce code avec un type incorrect, vous auriez un avertissement intéressant au lieu d'un comportement indéfini:

>>>

>>> ajoute un(1)
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
ctypes.ArgumentError: argument 1: : 
instance LP_c_int attendue au lieu de int

Python génère une erreur en expliquant que ajoute un() veut un pointeur au lieu d'un entier. Heureusement, types a un moyen de passer des pointeurs vers ces fonctions. Commençons par déclarer un entier de style C:

>>>

>>> X = types.c_int()
>>> X
c_int (0)

Le code ci-dessus crée un entier de style C X avec une valeur de 0. types fournit le pratique byref () pour permettre de passer une variable par référence.

Vous pouvez l'utiliser pour appeler ajoute un():

>>>

>>> ajoute un(types.byref(X))
998793640
>>> X
c_int (1)

Agréable! Votre entier a été incrémenté de un. Félicitations, vous avez utilisé avec succès de véritables pointeurs en Python.

Conclusion

Vous avez maintenant une meilleure compréhension de l'intersection entre les objets Python et les pointeurs. Bien que certaines distinctions entre les noms et les variables semblent pédantes, la compréhension de ces termes clés approfondit votre compréhension de la façon dont Python gère les variables.

Vous avez également appris d’excellents moyens de simuler des pointeurs en Python:

  • Utilisation d'objets mutables comme pointeurs à faible surcharge
  • Création d'objets Python personnalisés pour une facilité d'utilisation
  • Déverrouiller de vrais pointeurs avec le types module

Ces méthodes vous permettent de simuler des pointeurs en Python sans sacrifier la sécurité de la mémoire fournie par Python.

Merci d'avoir lu. Si vous avez encore des questions, n'hésitez pas à contacter la section commentaires ou Twitter.