web-dev-qa-db-fra.com

Des pointeurs en Python?

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

>>> a = 1
>>> b = a # modify this line somehow so that b "points to" 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 tout à fait nécessaire, mais je pense que ce serait bien.

100
mpen

Je veux form.data['field'] et form.field.value pour toujours avoir le même valeur

C’est faisable, car cela implique des noms décorés et l’indexation - c’est-à-dire, complètement différentes constructions du barenames a et b à propos desquelles vous vous posez des questions et pour lesquelles votre demande est tout à fait impossible. Pourquoi demander quelque chose d'impossible et totalement différent de la chose (possible) que vous avez en fait voulez ?!

Peut-être que vous ne réalisez pas à quel point les noms de prénom et noms décorés sont radicalement différents. Lorsque vous vous référez à un nom de barre a, vous obtenez exactement l'objet dont la variable a était liée pour la dernière fois dans cette étendue (ou une exception si elle n'était pas liée dans cette étendue). Il s'agit d'un aspect tellement fondamental et fondamental de Python que il ne peut pas éventuellement être subverti. Lorsque vous vous référez à un décoré nom x.y, vous demandez à un objet (auquel renvoie l'objet x) de bien vouloir fournir "l'attribut y" - et en réponse à cette demande, l'objet peut exécuter totalement calculs arbitraires (et l'indexation est assez similaire: elle permet également d'effectuer des calculs arbitraires en réponse).

Maintenant, votre exemple de "desiderata" est mystérieux parce que, dans chaque cas, deux niveaux d’indexation ou d’attribution d’attributs sont impliqués, de sorte que la subtilité dont vous rêvez pourrait être introduite de nombreuses manières. Quels autres attributs form.field est-il supposé avoir, par exemple, à part value? Sans ces calculs supplémentaires de .value, les possibilités seraient les suivantes:

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 formulaire, plus un type d’emballage inutile:

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

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

Si assignations __ __ form.field.value = 23 est également supposé définir l'entrée dans form.data, alors le wrapper doit devenir plus complexe, et pas si 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 s'apparente plus ou moins, en Python, au sens de "pointeur" que vous semblez vouloir - mais il est essentiel de comprendre que de telles subtilités ne peuvent fonctionner qu'avec indexation et/ou noms décorés, jamais avec les noms de prénoms tels que demandés à l'origine!

43
Alex Martelli

Vous ne pouvez en aucun cas changer 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 utilise également 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.

45
Matthew Flaschen

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

Quand vous regardez l'opérateur '=' dans Python, ne pensez pas en termes d'affectation. Vous n'attribuez pas de 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 des langages de type C, cela peut prêter à confusion, mais une fois que vous vous y êtes habitué, vous découvrez que cela vous aide à lire et à raisonner plus clairement sur votre code: la valeur qui porte le nom 'b' ne changera que si vous le souhaitez. le changer explicitement. Et si vous importez ceci, vous constaterez que le Zen of Python stipule 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.

34
David Harks

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

Je suis désolé de dire que beaucoup de 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 existe des variables dont le comportement égal ("=") fonctionne dans le dernier terme en tant que copie de l’espace mémoire, principalement dans des objets simples (par exemple, un objet "int"), et d’autres non (par exemple, des objets "list", "dict") .

Voici un exemple d'assignation de pointeur

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

Voici un exemple d'assignation 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’attribution de pointeurs est un outil très utile pour créer un alias sans gaspillage 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 cet usage pour éviter les erreurs de code.

Pour conclure, certaines variables sont par défaut des noms de fichiers (objets simples tels que int, float, str, ...), et certaines sont des pointeurs assignés entre elles (par exemple, dict1 = dict2). Comment les reconnaître? juste essayer cette expérience avec eux. Dans les environnements de développement intégrés avec panneau d’explorateur de variables, il apparaît généralement que l’adresse de mémoire ("@axbbbbbb ...") est définie dans la définition des objets de mécanisme de pointeur.

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

21

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 form.data ['field'] et form.field.value pour toujours avoir le même valeur. Ce n'est pas complètement nécessaire, mais je pense que ce serait Agréable.

Vous pouvez le faire en surchargeant __getitem__ dans la classe de form.data.

11
dan04
id(1)
1923344848  # identity of the location in my memory    
>> a = 1
>> b = a  # or equivalently b = 1, because 1 is immutable
>> id(a)
1923344848
>> id(b)
1923344848

Comme vous pouvez le constater, a et b ne sont que des noms qui font référence au même objet 1. Si, par la suite, vous écrivez a = 2, vous réaffectez le nom a à un autre objet 2, mais pas à la b qui continuera à faire référence à 1:

>> id(2)
1923344880
>> a = 2
>> id(a)
1923344880  # same as id(2)
>> id(b)
1923344848  # same as id(1)

Que se passera-t-il si vous aviez un objet mutable? 

>> id([1])
328817608
>> id([1])
328664968  # different
>> a = [1]
>> id(a)
328817800
>> id(a)
328817800  # same as before
>> b = a  # not equivalent to b = [1]
>> id(b)
328817800  # same as id(a)

Vous faites maintenant référence au même objet de liste par les noms a et b. Vous pouvez modifier cette liste, mais il restera le même objet et a et b continueront d'y faire référence.

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

J'ai écrit la classe simple suivante comme moyen efficace 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 (tiré d'une page du carnet de notes 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 entendu, il est également facile de faire en sorte que cela fonctionne pour les éléments de dictée ou les attributs d’un objet. Il y a même un moyen de faire ce que le PO 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 va imprimer 2, suivi de 3.

Cette façon de jouer avec l’espace de noms global est en quelque sorte une idée terriblement transparente, mais elle montre qu’il est possible (si cela n’est pas conseillé) de faire ce que le PO a demandé. 

L'exemple est bien sûr 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 paramétrables par l’utilisateur, de types variés (qui, du fait qu’ils dépendent d’arguments de ligne de commande, ne sont pas connus). au moment de la compilation). Et une fois l'accès à quelque chose 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++, il s’agit de résoudre un problème que j’aurais résolu avec des pointeurs si j’écrivais en C++. 

0
Leon Avery

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, incluant 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
0
MikeTeX