155 votes

Pointeurs en Python?

Je sais que Python n'a pas de pointeurs, mais y a-t-il un moyen d'obtenir ceci 2 à la place

>>> a = 1
>>> b = a # modifier cette ligne de quelque manière que ce soit pour que b "pointe vers" a
>>> a = 2
>>> b
1

?


Voici un exemple: je veux que form.data['field'] et form.field.value aient toujours la même valeur. Ce n'est pas complètement nécessaire, mais je pense que ce serait bien.


En PHP, par exemple, je peux faire ceci:

fields = $fields;
        foreach($this->fields as &$field) {
            $this->data[$field['id']] = &$field['value'];
        }
    }
}

$f = new Form([
    [
        'id' => 'fname',
        'value' => 'George'
    ],
    [
        'id' => 'lname',
        'value' => 'Lucas'
    ]
]);

echo $f->data['fname'], $f->fields[0]['value']; # George George
$f->data['fname'] = 'Ralph';
echo $f->data['fname'], $f->fields[0]['value']; # Ralph Ralph

Output:

GeorgeGeorgeRalphRalph

ideone


Ou comme ceci en C++ (Je pense que c'est correct, mais mon C++ est rouillé):

#include 
using namespace std;

int main() {
    int a;
    int* b = &a;
    *a = 1;
    cout << a << endl << *b << endl; # 1 1

    return 0;
}

30 votes

Peut-être que je peux poser une question similaire à celle de S.Lott (mais plus productive) : pouvez-vous nous montrer un vrai code où vous vouliez faire cela ? Peut-être même dans une autre langue qui correspond davantage à vos goûts ? Il est probable que le problème que vous essayez de résoudre se prête à une solution plus pythonique, et se concentrer sur "je veux des pointeurs" obscurcit la vraie réponse.

10 votes

Il ne faut pas beaucoup d'imagination; je peux penser à des dizaines de raisons pour vouloir le faire. Ce n'est tout simplement pas comme ça que cela se passe dans les langages sans pointeur comme Python; vous devez l'encadrer dans un conteneur qui n'est pas invariant, comme dans la réponse de Matt.

0 votes

@Ned : Question mise à jour. Un autre exemple serait pour une fonction, swap(a, b). Pas que j'en ai besoin tout de suite.

80voto

Matthew Flaschen Points 131723

Il n'y a aucun moyen que vous puissiez faire cela en ne changeant que cette ligne. Vous pouvez faire :

a = [1]
b = a
a[0] = 2
b[0]

Cela crée une liste, assigne la référence à a, puis à b aussi, utilise la référence a pour définir le premier élément à 2, puis y accède en utilisant la variable de référence b.

27 votes

C'est précisément le genre d'incohérence que je déteste à propos de Python et de ces langages dynamiques. (Oui oui, ce n'est pas vraiment "incohérent" car vous modifiez un attribut au lieu de la référence mais je n'aime toujours pas ça)

14 votes

@Mark: en effet. Je connais d'innombrables (enfin, quelques) personnes qui ont passé des heures à chercher un "bug" dans leur code pour ensuite découvrir que cela était causé par une liste qui n'avait pas été copiée en dur.

24 votes

Il n'y a aucune incohérence. Et cela n'a rien à voir avec le grand débat statique contre dynamique. S'il s'agissait de deux références au même Java ArrayList, ce serait la même chose, modulo la syntaxe. Si vous utilisez des objets immuables (comme les tuples), vous n'avez pas à vous soucier que l'objet soit modifié par une autre référence.

54voto

Alex Martelli Points 330805

Je veux que form.data['field'] et form.field.value aient toujours la même valeur

C'est faisable, car cela implique des noms décorés et de l'indexation -- c'est-à-dire des constructions complètement différentes des noms seuls a et b dont vous parlez, et pour lesquelles votre demande est totalement impossible. Pourquoi demander quelque chose d'impossible et totalement différent de la chose (possible) que vous voulez réellement ?!

Peut-être que vous ne réalisez pas à quel point les noms seuls et les noms décorés sont radicalement différents. Lorsque vous faites référence à un nom seul a, vous obtenez exactement l'objet auquel a était lié en dernier dans cette portée (ou une exception s'il n'était pas lié dans cette portée) -- c'est un aspect si profond et fondamental de Python qu'il ne peut être détourné. Lorsque vous faites référence à un nom décoré x.y, vous demandez à un objet (l'objet auquel x fait référence) de fournir "l'attribut y" -- et en réponse à cette demande, l'objet peut effectuer des calculs totalement arbitraires (et l'indexation est assez similaire: elle permet également d'effectuer des calculs arbitraires en réponse).

Maintenant, votre exemple de "véritables désidératas" est mystérieux car dans chaque cas, deux niveaux d'indexation ou d'obtention d'attributs sont impliqués, donc la subtilité que vous recherchez pourrait être introduite de plusieurs manières. Quels autres attributs form.field est censé avoir, par exemple, en plus de value ? Sans ces calculs supplémentaires en .value, les possibilités pourraient inclure:

class Form(object):
   ...
   def __getattr__(self, name):
       return self.data[name]

et

class Form(object):
   ...
   @property
   def data(self):
       return self.__dict__

La présence de .value suggère de choisir le premier exemple, plus un wrapper un peu inutile :

class KouWrap(object):
   def __init__(self, value):
       self.value = value

class Form(object):
   ...
   def __getattr__(self, name):
       return KouWrap(self.data[name])

Si des affectations telles que form.field.value = 23 doivent également définir l'entrée dans form.data, alors le wrapper doit effectivement devenir plus complexe, et pas si inutile que cela :

class MciWrap(object):
   def __init__(self, data, k):
       self._data = data
       self._k = k
   @property
   def value(self):
       return self._data[self._k]
   @value.setter
   def value(self, v)
       self._data[self._k] = v

class Form(object):
   ...
   def __getattr__(self, name):
       return MciWrap(self.data, name)

Le dernier exemple est à peu près ce qui se rapproche le plus, en Python, du sens d'un "pointeur" tel que vous semblez le vouloir -- mais il est crucial de comprendre que de telles subtilités ne peuvent jamais fonctionner qu'avec des index et/ou des noms décorés, jamais avec des noms seuls comme vous l'avez demandé initialement !

29 votes

J'ai posé la question de la manière dont je l'ai fait parce que j'espérais obtenir une solution générale qui fonctionnerait pour tout, jusqu'aux "barenames", et je n'avais pas de cas particulier en tête à ce moment-là - juste que j'avais rencontré ce problème avec l'itération précédente de ce projet et je ne voulais pas y être confronté à nouveau. Quoi qu'il en soit, c'est une excellente réponse! L'incrédulité, cependant, est moins appréciée.

2 votes

@Mark, regardez les commentaires sur votre question et vous verrez que "incrédulité" est une réaction répandue -- et la réponse la mieux notée vous dit "ne le faites pas, passez à autre chose", suivie par une autre qui dit "c'est juste comme ça". Alors que vous n'êtes peut-être pas "apprécier" les réactions d'étonnement des personnes connaissant Python par rapport à vos spécifications originales, elles sont assez étonnantes en elles-mêmes;-).

32 votes

Oui, mais tu sembles étonné de mon manque de connaissance en Python... comme si nous étions une espèce étrangère :P

40voto

Ce n'est pas un bug, c'est une fonctionnalité :-)

Lorsque vous regardez l'opérateur '=' en Python, ne pensez pas en termes d'assignation. Vous n'assignez pas les choses, vous les liez. = est un opérateur de liaison.

Donc, dans votre code, vous donnez la valeur 1 un nom : a. Ensuite, vous donnez le nom 'a' à la valeur dans 'a': b. Ensuite, vous liez la valeur 2 au nom 'a'. La valeur liée à b ne change pas dans cette opération.

Venant des langages de type C, cela peut être déroutant, mais une fois habitué, vous constaterez que cela vous aide à lire et à raisonner sur votre code plus clairement : la valeur qui porte le nom 'b' ne changera pas à moins que vous ne la changiez explicitement. Et si vous faites un 'import this', vous trouverez que le Zen de Python indique que l'Explicite est meilleur que l'implicite.

Notez également que les langages fonctionnels tels que Haskell utilisent également ce paradigme, avec une grande valeur en termes de robustesse.

46 votes

Tu sais, j'ai lu des réponses comme celle-ci des dizaines de fois, et je ne l'ai jamais comprise. Le comportement de a = 1; b = a; a = 2; est exactement le même en Python, en C et en Java: b vaut 1. Pourquoi cette focalisation sur "= n'est pas une affectation, c'est un lien"?

4 votes

Vous affectez des choses. C'est pourquoi on l'appelle une instruction d'affectation. La distinction que vous faites n'a aucun sens. Et cela n'a rien à voir avec compilé ou interprété, statique ou dynamique. Java est un langage compilé avec une vérification de type statique, et il n'a pas non plus de pointeurs.

5 votes

Qu'en est-il de C++? "b" pourrait être une référence à "a". Comprendre la différence entre l'attribution et la liaison est essentiel pour comprendre pleinement pourquoi Mark ne peut pas faire ce qu'il voudrait faire, et comment les langages comme Python sont conçus. Conceptuellement (pas nécessairement dans l'implémentation), "a = 1" ne remplace pas le bloc de mémoire nommé "a" par 1; il attribue un nom "a" à l'objet déjà existant "1", ce qui est fondamentalement différent de ce qui se passe en C. C'est pourquoi les pointeurs en tant que concept ne peuvent pas exister en Python - ils deviendraient obsolètes la prochaine fois que la variable d'origine était "assignée par-dessus".

28voto

Oui! il y a un moyen d'utiliser une variable comme un pointeur en python!

Je suis désolé de dire que beaucoup de réponses étaient partiellement incorrectes. En principe, chaque assignation égale (=) partage l'adresse mémoire (vérifiez la fonction id(obj)), mais en pratique ce n'est pas le cas. Il y a des variables dont le comportement égal ("=") fonctionne en fin de compte comme une copie de l'espace mémoire, principalement dans les objets simples (par exemple l'objet "int"), et d'autres pour lesquels ce n'est pas le cas (par exemple les objets "list","dict").

Voici un exemple d'assignation de pointeur

dict1 = {'premier':'bonjour', 'deuxième':'monde'}
dict2 = dict1 # mécanisme d'assignation de pointeur
dict2['premier'] = 'au revoir'
dict1
>>> {'premier':'au revoir', 'deuxième':'monde'}

Voici un exemple d'assignation de copie

a = 1
b = a # mécanisme de copie de mémoire. jusqu'ici id(a) == id(b)
b = 2 # génération de nouvelle adresse. donc sans comportement de pointeur
a
>>> 1

L'assignation de pointeur est un outil plutôt utile pour l'aliasing sans gaspillage de mémoire supplémentaire, dans certaines situations pour exécuter du code confortablement,

class cls_X():
   ...
   def method_1():
      pd1 = self.obj_clsY.dict_vars_for_clsX['meth1'] # pointeur dict 1: aliasing
      pd1['var4'] = self.method2(pd1['var1'], pd1['var2'], pd1['var3'])
   #enddef method_1
   ...
#endclass cls_X

mais il faut être conscient de cet usage afin d'éviter les erreurs de code.

En conclusion, par défaut certaines variables sont des noms nus (objets simples comme int, float, str,...), et certaines sont des pointeurs lorsqu'elles sont assignées entre elles (par exemple dict1 = dict2). Comment les reconnaître? faites simplement cette expérience avec elles. Dans les IDE avec un panneau d'explorateur de variables, l'adresse mémoire ("@axbbbbbb...") apparaît généralement dans la définition des objets de mécanisme de pointeur.

Je suggère d'enquêter sur le sujet. Il y a certainement beaucoup de personnes qui en savent beaucoup plus sur ce sujet. (voir le module "ctypes"). J'espère que cela est utile. Profitez du bon usage des objets! Cordialement, José Crespo

2 votes

Donc je dois utiliser un dictionnaire pour passer une variable par référence à une fonction, et je ne peux pas passer une variable par référence en utilisant un int ou une string?

2 votes

Ceci est incorrect. Il n'y a qu'un seul type d'assignation de variable en Python. Votre exemple démontre en fait que les dictionnaires sont mutables tandis que les entiers sont immuables. Lorsque vous faites dict2 = dict1, les deux variables font référence à la même adresse en mémoire, ce que vous pouvez confirmer avec la fonction id(). Et la même chose est vraie pour l'exemple int b = a. La différence est que vous réassignez l'identifiant b dans le deuxième cas, mais vous mutez l'objet dict2 (qui est le même que dict1) dans le premier cas. Le comportement de foo = bar est exactement le même pour chaque type.

0 votes

(Pour clarifier ce qui précède) la différence se trouve dans la partie où vous faites dict2["first"] = "bye"; c'est là que vous modifiez l'objet sous-jacent, ce que vous ne pouvez pas faire avec l'entier. Jusqu'à ce moment-là, l'instruction d'assignation fonctionnait de la même manière. dict2["first"] = "bye" fonctionne différemment parce que la chose à gauche n'est pas une variable, c'est un lvalue plus général. Le = invoque la méthode __setitem__ sur le dictionnaire. Cela se termine par réaffecter à quoi fait référence la clé "first" de l'objet dictionnaire, modifiant ainsi l'objet dict2. Voir cette discussion : youtube.com/watch?v=_AEJHKGk9ns

24voto

AndyK Points 1677
>> id(1)
1923344848  # identité de l'emplacement en mémoire où est stocké 1
>> id(1)
1923344848  # toujours le même
>> a = 1
>> b = a  # ou de manière équivalente b = 1, car 1 est immuable
>> id(a)
1923344848
>> id(b)  # égal à id(a)
1923344848

Comme vous pouvez le constater, a et b sont simplement deux noms différents qui font référence au même objet immuable (int) 1. Si plus tard vous écrivez a = 2, vous réaffectez le nom a à un autre objet (int) 2, mais le b continue de faire référence à 1:

>> id(2)
1923344880
>> a = 2
>> id(a)
1923344880  # égal à id(2)
>> b
1           # b n'a pas changé
>> id(b)
1923344848  # égal à id(1)

Que se passerait-il si vous aviez un objet mutable à la place, comme une liste [1] ?

>> id([1])
328817608
>> id([1])
328664968  # différent du précédent id, car une nouvelle liste est créée à chaque fois
>> a = [1]
>> id(a)
328817800
>> id(a)
328817800 # maintenant identique à avant
>> b = a
>> id(b)
328817800  # identique à id(a)

Encore une fois, nous faisons référence au même objet (liste) [1] avec deux noms différents a et b. Cependant, cette fois-ci, nous pouvons muter cette liste tout en restant le même objet, et a, b continueront tous deux de lui faire référence

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # comme avant
>> id(b)
328817800  # comme avant

2 votes

Merci d'avoir introduit la fonction id. Cela résout bon nombre de mes doutes.

0 votes

Y a-t-il un moyen de faire en sorte que b pointe toujours vers ce que pointe a ? c'est-à-dire que si je disais a = 2, alors b cesserait automatiquement de pointer vers 1 et pointerait plutôt vers 2.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X