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
Remarque: Dans cet article, «Python» fera référence à l'implémentation de référence de Python en C, également appelée CPython. Dans la mesure où l'article traite de certaines caractéristiques internes du langage, ces remarques sont vraies pour CPython 3.7, mais peuvent ne pas l'être lors d'itérations futures ou passées du langage.
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:
- Objets immuables vs mutables
- 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:
- Objets immuables ne peut pas être changé.
- 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:
id ()
renvoie l'adresse mémoire de l'objet.est
résultatsVrai
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.
Prime: le + =
l'opérateur se traduit par divers appels de méthode.
Pour certains objets comme liste
, + =
traduira en __J'ajoute__()
(ajouter sur place). Cela modifiera soi
et renvoyer le même identifiant. cependant, str
et int
ne pas avoir ces méthodes et aboutir à __ajouter__()
appels au lieu de __J'ajoute__()
.
Pour plus d'informations, consultez les documents relatifs au modèle de données Python.
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:
- Allouez assez de mémoire pour un entier
- Attribuer la valeur
2337
à cet emplacement de mémoire - Indique que
X
pointe vers cette valeur
Dans une vue simplifiée de la mémoire, cela pourrait ressembler à ceci:
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:
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:
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:
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:
- Créer un
PyObject
- Définissez le type de code sur entier pour le
PyObject
- Définissez la valeur sur
2337
pour lePyObject
- Créez un nom appelé
X
- Point
X
à la nouvellePyObject
- Augmenter le nombre de
PyObject
par 1
Remarque: le PyObject
n'est pas la même chose que Python objet
. Il est spécifique à CPython et représente la structure de base de tous les objets Python.
PyObject
est défini comme une structure C, donc si vous vous demandez pourquoi vous ne pouvez pas appeler type de code
ou recomptage
directement, c’est parce que vous n’avez pas directement accès aux structures. Appels de méthode comme sys.getrefcount ()
peut aider à obtenir des internes.
En mémoire, cela pourrait ressembler à quelque chose comme ça:
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 lePyObject
- Points
X
à la nouvellePyObject
- 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:
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:
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:
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é:
- Créer un objet Python (
1000
) - Attribuer le nom
X
à cet objet - Créer un objet Python (
499
) - Créer un objet Python (
501
) - Ajouter ces deux objets ensemble
- Créer un nouvel objet Python (
1000
) - Attribuer le nom
y
à cet objet
Note technique: Les étapes ci-dessus ne se produisent que lorsque ce code est exécuté dans un REPL. Si vous preniez l'exemple ci-dessus, collez-le dans un fichier et exécutez-le, vous constateriez que le x est y
la ligne reviendrait Vrai
.
Cela se produit parce que les compilateurs sont intelligents. Le compilateur CPython tente de réaliser des optimisations appelées optimisations de judas, qui permettent d’enregistrer les étapes d’exécution dans la mesure du possible. Pour plus d’informations, vous pouvez consulter le code source de l’optimiseur de perforation de CPython.
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:
- Nombre entier entre
-5
et256
- 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.
Prime: Si vous voulez vraiment que ces objets fassent référence au même objet interne, vous pouvez vérifier sys.intern ()
. L'un des cas d'utilisation de cette fonction est décrit dans la documentation:
L'internation de chaînes est utile pour gagner un peu en performances en matière de consultation de dictionnaire: si les clés d'un dictionnaire sont internées et que la clé de recherche est internée, les comparaisons de clé (après hachage) peuvent être effectuées au moyen d'un pointeur au lieu d'une comparaison de chaîne. (La source)
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:
- Utiliser des types mutables comme pointeurs
- 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:
inc_func_calls ()
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.
Remarque: Dans cette classe en particulier, faire inc_func_calls ()
et inc_cat_pics ()
explicite au lieu d'utiliser @ property.setter
empêche les utilisateurs de définir ces valeurs à un arbitraire int
ou une valeur invalide comme un dict
.
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.
Remarque: Le terme par référence s'oppose à passer une variable par valeur.
Lorsque vous passez par référence, vous transmettez la référence à la variable d'origine et les modifications seront donc répercutées dans la variable d'origine. En passant par valeur, on obtient une copie de la variable d'origine et les modifications ne sont pas répercutées dans l'original.
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.
[ad_2]