Des pointeurs en Python?


124

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

>>> a = 1
>>> b = a # modify this line somehow so that b "points to" a
>>> a = 2
>>> b
1

?


Voici un exemple: je veux form.data['field']et form.field.valueavoir toujours la même valeur. Ce n'est pas tout à fait nécessaire, mais je pense que ce serait bien.


En PHP, par exemple, je peux faire ceci:

<?php

class Form {
    public $data = [];
    public $fields;

    function __construct($fields) {
        $this->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

Production:

GeorgeGeorgeRalphRalph

idéone


Ou comme ça en C ++ (je pense que c'est vrai, mais mon C ++ est rouillé):

#include <iostream>
using namespace std;

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

    return 0;
}

28
Peut-être puis-je poser une question similaire à celle de S.Lott (mais plus productive): pouvez-vous nous montrer du vrai code où vous vouliez faire cela? Peut-être même dans une autre langue plus à votre goût? Il est probable que le problème que vous essayez de résoudre se prêtera à une solution plus pythonique, et se concentrer sur "Je veux des pointeurs" obscurcit la vraie réponse.
Ned Batchelder

8
Cela ne demande pas beaucoup d'imagination; Je peux penser à des dizaines de raisons de vouloir faire cela. Ce n'est tout simplement pas ainsi que cela se fait dans des langages sans pointeur comme Python; vous devez l'envelopper dans un conteneur qui n'est pas invariant, comme dans la réponse de Matt.
Glenn Maynard

14
Vous n'écririez pas de fonction d'échange en Python. Vous écririez a, b = b, a.
dan04

3
-1: Le construit dans la question est (a) insensé, et (b) personne ne semble être en mesure de fournir un exemple qui le rend sensible. Dire qu'il y a «des dizaines de raisons» n'est pas la même chose que publier un exemple.
S.Lott

1
@Mark: De plus, je ne suis pas "en désaccord". Je suis confus. Je pose une question, je cherche un moyen de comprendre ce que c'est et pourquoi vous pensez que c'est si important.
S.Lott

Réponses:


48

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

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

Peut-être que vous ne réalisez pas à quel point les noms de barres et les noms décorés sont radicalement différents. Lorsque vous faites référence à un nom de barre a, vous obtenez exactement l'objet auquel a aété lié la dernière fois dans cette portée (ou une exception s'il n'a pas été lié dans cette portée) - c'est un aspect si profond et fondamental de Python qu'il peut peut-être pas être subverti. Lorsque vous faites référence à un nom décoréx.y , vous demandez à un objet ( auquel l'objet xfait référence) de s'il vous plaît fournir "l' yattribut" - et en réponse à cette demande, l'objet peut effectuer des calculs totalement arbitraires (et l'indexation est assez similaire: il permet également d'effectuer des calculs arbitraires en réponse).

Maintenant, votre exemple de "desiderata réel" est mystérieux parce que dans chaque cas, deux niveaux d'indexation ou d'obtention d'attributs sont impliqués, donc la subtilité dont vous rêvez pourrait être introduite de plusieurs manières. Quels autres attributs est form.fieldcensé avoir, par exemple, en plus value? Sans ces .valuecalculs supplémentaires , les possibilités comprendraient:

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 .valuesuggère de choisir la première forme, plus une sorte de wrapper inutile:

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

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

Si de telles affectationsform.field.value = 23 sont également censées définir l'entrée form.data, alors le wrapper doit devenir plus complexe en effet, et pas du tout inutile:

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)

Ce dernier exemple est à peu près aussi proche que possible, en Python, du sens de "pointeur" que vous semblez vouloir - mais il est crucial de comprendre que de telles subtilités ne peuvent jamais fonctionner qu'avec des noms indexés et / ou décorés , jamais avec des noms de barres comme vous l'avez demandé!


23
Je l'ai demandé comme je l'ai fait parce que j'espérais obtenir une solution générale qui fonctionnerait pour tout, jusqu'aux "noms de bar" et je n'avais pas de cas particulier en tête à l'époque - juste que j'avais rencontré cela problème avec l'itération précédente de ce projet et je ne voulais pas rencontrer à nouveau. Quoi qu'il en soit, c'est une excellente réponse! L'incrédulité, cependant, est moins appréciée.
mpen

2
@Mark, regardez les commentaires sur votre Q et vous verrez que "l'incrédulité" est une réaction répandue - et le A le mieux voté vous dit "ne le faites pas, surmontez-le", suivi par un qui dit "c'est juste comme ça". Bien que vous n'appréciez peut-être pas que les gens connaissant Python réagissent avec étonnement à vos spécifications d'origine, ils sont assez étonnants en eux-mêmes ;-).
Alex Martelli

30
Oui, mais vous semblez étonné de mon manque de connaissances sur Python ... comme si nous étions une espèce étrangère: P
mpen

51

Il n'y a aucun moyen que vous puissiez faire cela en changeant uniquement cette ligne. Tu peux faire:

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

Cela crée une liste, attribue la référence à a, puis b également, utilise la référence a pour définir le premier élément sur 2, puis accède à l'aide de la variable de référence b.


17
C'est exactement le genre d'incohérence que je déteste à propos de Python et de ces langages dynamiques. (Ouais ouais, ce n'est pas vraiment "incohérent" parce que vous changez un attribut plutôt que la référence mais je n'aime toujours pas ça)
mpen

10
@Mark: en effet. Je connais d'innombrables (enfin, quelques) personnes qui ont passé peut-être des heures à rechercher un "bogue" dans leur code, puis à découvrir qu'il était causé par une liste qui n'était pas copiée sur papier.
houbysoft

14
Il n'y a pas d'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 à la même liste Java ArrayList, ce serait la même syntaxe modulo. Si vous utilisez des objets immuables (comme des tuples), vous n'avez pas à vous soucier de la modification de l'objet via une autre référence.
Matthew Flaschen

Je l'ai utilisé plusieurs fois, le plus souvent pour contourner le manque de "non local" de 2.x. Ce n'est pas la chose la plus jolie à faire, mais cela fonctionne très bien à la rigueur.
Glenn Maynard

1
Ce n'est pas du tout incompatible parce que l'objet que vous assignez à aet bla liste, pas la valeur dans la liste. Les variables ne changent pas d'affectation, l'objet est le même objet. S'il a changé, comme dans le cas de la modification de l'entier (dont chacun est des objets différents), il aserait maintenant attribué à un autre objet et rien n'incite bà suivre. Ici, ce an'est pas réaffecté, mais plutôt une valeur à l'intérieur de l'objet auquel il est affecté change. Puisqu'il best toujours lié à cet objet, il reflétera cet objet et les modifications des valeurs qu'il contient.
arkigos

34

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

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

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

Venant de langages de type C, cela peut être déroutant, mais une fois que vous vous y êtes habitué, vous constatez que cela vous aide à lire et à raisonner plus clairement sur votre code: la valeur qui porte le nom `` b '' ne changera pas sauf si vous le changer explicitement. Et si vous faites un 'import this', vous constaterez que le Zen de Python déclare qu'Explicit est meilleur qu'implicite.

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


39
Vous savez, 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, C et Java: b vaut 1. Pourquoi ce focus sur "= n'est pas une affectation, c'est une liaison"?
Ned Batchelder

4
Vous attribuez des choses. C'est pourquoi cela s'appelle une déclaration d'affectation . La distinction que vous dites n'a pas de sens. Et cela n'a rien à voir avec compilé v. Interprété ou statique v. Dynamique. Java est un langage compilé avec vérification de type statique, et il n'a pas non plus de pointeurs.
Matthew Flaschen

3
Et le C ++? "b" peut être une référence à "a". Comprendre la différence entre l'affectation et la liaison est essentiel pour comprendre pleinement pourquoi Mark ne peut pas faire ce qu'il aimerait faire et comment des langages comme Python sont conçus. Conceptuellement (pas nécessairement dans l'implémentation), "a = 1" n'écrase 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 le la variable d'origine a été «affectée».
Glenn Maynard

1
@dw: j'aime cette façon de penser! «Reliure» est un bon mot. @Ned: La sortie est la même, oui, mais en C la valeur de "1" est copiée dans les deux aet balors qu'en Python, ils font tous les deux référence au même "1" (je pense). Ainsi, si vous pouviez changer la valeur de 1 (comme pour les objets), ce serait différent. Ce qui conduit à des problèmes de boxe / déballage étranges, j'entends.
mpen

4
La différence entre Python et C n'est pas ce que signifie «affectation». C'est ce que signifie «variable».
dan04

28

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

Je suis désolé de dire que de nombreuses réponses étaient partiellement fausses. 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 dernier terme comme une copie de l'espace mémoire, principalement dans des objets simples (par exemple, un objet "int"), et d'autres dans lesquels non (par exemple des objets "liste", "dict") .

Voici un exemple d'affectation de pointeur

dict1 = {'first':'hello', 'second':'world'}
dict2 = dict1 # pointer assignation mechanism
dict2['first'] = 'bye'
dict1
>>> {'first':'bye', 'second':'world'}

Voici un exemple d'affectation de copie

a = 1
b = a # copy of memory mechanism. up to here id(a) == id(b)
b = 2 # new address generation. therefore without pointer behaviour
a
>>> 1

L'affectation de pointeurs est un outil assez utile pour créer des alias sans gaspiller de mémoire supplémentaire, dans certaines situations pour exécuter du code confortable,

class cls_X():
   ...
   def method_1():
      pd1 = self.obj_clsY.dict_vars_for_clsX['meth1'] # pointer 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 cette utilisation pour éviter les erreurs de code.

Pour conclure, par défaut, certaines variables sont des noms de barres (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? essayez simplement cette expérience avec eux. Dans les IDE avec le panneau de l'explorateur de variables, il semble généralement être l'adresse mémoire ("@axbbbbbb ...") dans la définition des objets du mécanisme de pointeur.

Je suggère d'enquêter sur le sujet. Il y a beaucoup de gens qui en savent bien plus sur ce sujet. (voir module "ctypes"). J'espère que c'est utile. Profitez de la bonne utilisation des objets! Cordialement, José Crespo


Je dois donc 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 chaîne?
Sam

13
>> id(1)
1923344848  # identity of the location in memory where 1 is stored
>> id(1)
1923344848  # always the same
>> a = 1
>> b = a  # or equivalently b = 1, because 1 is immutable
>> id(a)
1923344848
>> id(b)  # equal to id(a)
1923344848

Comme vous pouvez le voir, ail ne bs'agit que de deux noms différents qui font référence au même objet immuable (int) 1. Si vous écrivez plus tard a = 2, vous réaffectez le nom aà un objet différent (int) 2, mais bcontinue de faire référence à 1:

>> id(2)
1923344880
>> a = 2
>> id(a)
1923344880  # equal to id(2)
>> b
1           # b hasn't changed
>> id(b)
1923344848  # equal to id(1)

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

>> id([1])
328817608
>> id([1])
328664968  # different from the previous id, because each time a new list is created
>> a = [1]
>> id(a)
328817800
>> id(a)
328817800 # now same as before
>> b = a
>> id(b)
328817800  # same as id(a)

Encore une fois, nous faisons référence au même objet (liste) [1]par deux noms différents aet b. Mais maintenant , nous pouvons muter cette liste alors qu'il reste le même objet, et a, bsera à la fois continuer le référencement à ce

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # same as before
>> id(b)
328817800  # same as before

1
Merci d'avoir introduit la fonction id. Cela résout beaucoup de mes doutes.
haudoing

12

D'un point de vue, tout est un pointeur en Python. Votre exemple fonctionne beaucoup comme le code C ++.

int* a = new int(1);
int* b = a;
a = new int(2);
cout << *b << endl;   // prints 1

(Un équivalent plus proche utiliserait un type de shared_ptr<Object>au lieu de int*.)

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.

Vous pouvez le faire en surchargeant __getitem__dans form.datala classe de.


form.datan'est pas une classe. Est-il nécessaire d'en créer un ou puis-je le remplacer à la volée? (C'est juste un dict python) De plus, les données devraient avoir une référence formpour accéder aux champs ... ce qui rend l'implémentation moche.
mpen

1

Ceci est un pointeur python (différent de c / c ++)

>>> a = lambda : print('Hello')
>>> a
<function <lambda> at 0x0000018D192B9DC0>
>>> id(a) == int(0x0000018D192B9DC0)
True
>>> from ctypes import cast, py_object
>>> cast(id(a), py_object).value == cast(int(0x0000018D192B9DC0), py_object).value
True
>>> cast(id(a), py_object).value
<function <lambda> at 0x0000018D192B9DC0>
>>> cast(id(a), py_object).value()
Hello

0

J'ai écrit la classe simple suivante comme, en fait, un moyen d'émuler un pointeur en python:

class Parameter:
    """Syntactic sugar for getter/setter pair
    Usage:

    p = Parameter(getter, setter)

    Set parameter value:
    p(value)
    p.val = value
    p.set(value)

    Retrieve parameter value:
    p()
    p.val
    p.get()
    """
    def __init__(self, getter, setter):
        """Create parameter

        Required positional parameters:
        getter: called with no arguments, retrieves the parameter value.
        setter: called with value, sets the parameter.
        """
        self._get = getter
        self._set = setter

    def __call__(self, val=None):
        if val is not None:
            self._set(val)
        return self._get()

    def get(self):
        return self._get()

    def set(self, val):
        self._set(val)

    @property
    def val(self):
        return self._get()

    @val.setter
    def val(self, val):
        self._set(val)

Voici un exemple d'utilisation (à partir d'une page de notebook jupyter):

l1 = list(range(10))
def l1_5_getter(lst=l1, number=5):
    return lst[number]

def l1_5_setter(val, lst=l1, number=5):
    lst[number] = val

[
    l1_5_getter(),
    l1_5_setter(12),
    l1,
    l1_5_getter()
]

Out = [5, None, [0, 1, 2, 3, 4, 12, 6, 7, 8, 9], 12]

p = Parameter(l1_5_getter, l1_5_setter)

print([
    p(),
    p.get(),
    p.val,
    p(13),
    p(),
    p.set(14),
    p.get()
])
p.val = 15
print(p.val, l1)

[12, 12, 12, 13, 13, None, 14]
15 [0, 1, 2, 3, 4, 15, 6, 7, 8, 9]

Bien sûr, il est également facile de faire ce travail pour les éléments dict ou les attributs d'un objet. Il existe même un moyen de faire ce que l'OP a demandé, en utilisant globals ():

def setter(val, dict=globals(), key='a'):
    dict[key] = val

def getter(dict=globals(), key='a'):
    return dict[key]

pa = Parameter(getter, setter)
pa(2)
print(a)
pa(3)
print(a)

Cela imprimera 2, suivi de 3.

Jouer avec l'espace de noms global de cette manière est une sorte de transparence une idée terrible, mais cela montre qu'il est possible (si cela n'est pas conseillé) de faire ce que l'OP a demandé.

L'exemple est, bien entendu, assez inutile. Mais j'ai trouvé cette classe utile dans l'application pour laquelle je l'ai développée: un modèle mathématique dont le comportement est régi par de nombreux paramètres mathématiques réglables par l'utilisateur, de types divers (qui, parce qu'ils dépendent d'arguments de ligne de commande, ne sont pas connus au moment de la compilation). Et une fois que l'accès à quelque chose a été encapsulé dans un objet Parameter, tous ces objets peuvent être manipulés de manière uniforme.

Bien que cela ne ressemble pas beaucoup à un pointeur C ou C ++, cela résout un problème que j'aurais résolu avec des pointeurs si j'écrivais en C ++.


0

Le code suivant émule exactement le comportement des pointeurs en C:

from collections import deque # more efficient than list for appending things
pointer_storage = deque()
pointer_address = 0

class new:    
    def __init__(self):
        global pointer_storage    
        global pointer_address

        self.address = pointer_address
        self.val = None        
        pointer_storage.append(self)
        pointer_address += 1


def get_pointer(address):
    return pointer_storage[address]

def get_address(p):
    return p.address

null = new() # create a null pointer, whose address is 0    

Voici des exemples d'utilisation:

p = new()
p.val = 'hello'
q = new()
q.val = p
r = new()
r.val = 33

p = get_pointer(3)
print(p.val, flush = True)
p.val = 43
print(get_pointer(3).val, flush = True)

Mais il est maintenant temps de donner un code plus professionnel, y compris la possibilité de supprimer des pointeurs, que je viens de trouver dans ma bibliothèque personnelle:

# C pointer emulation:

from collections import deque # more efficient than list for appending things
from sortedcontainers import SortedList #perform add and discard in log(n) times


class new:      
    # C pointer emulation:
    # use as : p = new()
    #          p.val             
    #          p.val = something
    #          p.address
    #          get_address(p) 
    #          del_pointer(p) 
    #          null (a null pointer)

    __pointer_storage__ = SortedList(key = lambda p: p.address)
    __to_delete_pointers__ = deque()
    __pointer_address__ = 0 

    def __init__(self):      

        self.val = None 

        if new.__to_delete_pointers__:
            p = new.__to_delete_pointers__.pop()
            self.address = p.address
            new.__pointer_storage__.discard(p) # performed in log(n) time thanks to sortedcontainers
            new.__pointer_storage__.add(self)  # idem

        else:
            self.address = new.__pointer_address__
            new.__pointer_storage__.add(self)
            new.__pointer_address__ += 1


def get_pointer(address):
    return new.__pointer_storage__[address]


def get_address(p):
    return p.address


def del_pointer(p):
    new.__to_delete_pointers__.append(p)

null = new() # create a null pointer, whose address is 0

Je pense que vous venez d'encadrer les valeurs d'une manière étrange.
ouvert

Vous voulez dire: une «manière intelligente» ou une «manière non intelligente»?
MikeTeX

Euh ... J'ai du mal à voir un cas d'utilisation valide pour un stockage global indexé par un nombre aléatoire.
mpen

Exemple d'utilisation: je suis ingénieur en algorithmes et je dois travailler avec des programmeurs. Je travaille avec Python et ils travaillent avec C ++. Parfois, ils me demandent d'écrire un algorithme pour eux, et je l'écris aussi près que possible de C ++ pour leur commodité. Les pointeurs sont utiles par exemple pour les arbres binaires, etc.
MikeTeX

Remarque: si le stockage global vous dérange, vous pouvez l'inclure en tant que variable globale au niveau de la classe elle-même, ce qui est probablement plus élégant.
MikeTeX
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.