Puntatori in Python?


124

So che Python non ha puntatori, ma c'è un modo per avere 2invece questo rendimento

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

?


Ecco un esempio: voglio form.data['field']eform.field.value avere sempre lo stesso valore. Non è completamente necessario, ma penso che sarebbe carino.


In PHP, ad esempio, posso fare questo:

<?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

Produzione:

GeorgeGeorgeRalphRalph

Ideone


O come questo in C ++ (penso che sia giusto, ma il mio C ++ è arrugginito):

#include <iostream>
using namespace std;

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

    return 0;
}

28
Forse posso fare una domanda simile a quella di S.Lott (ma più produttiva): puoi mostrarci del codice reale dove volevi farlo? Forse anche in un'altra lingua che è più di tuo gusto? È probabile che il problema che stai cercando di risolvere si presti a una soluzione più pitonica e concentrarti su "Voglio i puntatori" sta oscurando la vera risposta.
Ned Batchelder

8
Non ci vuole molta immaginazione; Mi vengono in mente dozzine di ragioni per volerlo fare. Semplicemente non è come viene fatto in linguaggi senza puntatori come Python; devi avvolgerlo in un contenitore che non sia invariante, come nella risposta di Matt.
Glenn Maynard

14
Non scriveresti una funzione di scambio in Python. Scriveresti a, b = b, a.
dan04

3
-1: Il costrutto nella domanda è (a) privo di senso e (b) nessuno sembra essere in grado di fornire un esempio che lo renda ragionevole. Dire che ci sono "dozzine di ragioni" non è la stessa cosa che pubblicare un esempio.
S.Lott

1
@Mark: Inoltre, non sono "in disaccordo". Non ho capito bene. Sto facendo una domanda, cercando un modo per capire cos'è e perché pensi che sia così importante.
S.Lott

Risposte:


48

Voglio form.data['field']e form.field.valueavere sempre lo stesso valore

Questo è fattibile, perché coinvolge nomi decorati e indicizzazione - cioè, costrutti completamente diversi dai nomi dei bara e di bcui stai chiedendo, e con la tua richiesta è assolutamente impossibile. Perché chiedere qualcosa di impossibile e totalmente diverso dal (possibile) cosa che in realtà vuoi ?!

Forse non ti rendi conto di quanto siano drasticamente diversi i nomi dei bar ei nomi decorati. Quando fai riferimento a un nome a barre a, ottieni esattamente l'oggetto a cui aera stato associato per ultimo in questo ambito (o un'eccezione se non era associato in questo ambito): questo è un aspetto così profondo e fondamentale di Python che può possibilmente essere sovvertito. Quando fai riferimento a un nome decoratox.y , stai chiedendo a un oggetto (l'oggetto a cui si xriferisce) di fornire "l' yattributo" - e in risposta a tale richiesta, l'oggetto può eseguire calcoli totalmente arbitrari (e l'indicizzazione è abbastanza simile: consente inoltre di eseguire calcoli arbitrari in risposta).

Ora, il tuo esempio di "desiderata reale" è misterioso perché in ogni caso sono coinvolti due livelli di indicizzazione o acquisizione di attributi, quindi la sottigliezza che desideri potrebbe essere introdotta in molti modi. Quali altri attributi form.fielddovrebbe avere, ad esempio, oltre value? Senza che ulteriori .valuecalcoli, le possibilità includerebbero:

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

e

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

La presenza di .valuesuggerisce di scegliere la prima forma, più una sorta di involucro inutile:

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

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

Se si suppone che anche assegnazioni di questo tipo form.field.value = 23impostino la voce form.data, allora il wrapper deve diventare davvero più complesso, e non del tutto 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)

Quest'ultimo esempio è più o meno quanto di più vicino si avvicina, in Python, al senso di "un puntatore" come sembra tu voglia - ma è cruciale capire che tali sottigliezze possono sempre funzionare solo con l' indicizzazione e / o i nomi decorati , mai con i nomi dei bar come originariamente chiesto!


23
L'ho chiesto nel modo in cui l'ho fatto perché speravo di ottenere una soluzione generale che funzionasse per tutto, fino ai "barenames" e non avevo in mente un caso particolare, solo che mi ero imbattuto in questo problema con l'iterazione precedente di questo progetto e non volevo imbattermi di nuovo. Comunque, questa è un'ottima risposta! L'incredulità, invece, è meno apprezzata.
mpen

2
@Mark, guarda i commenti sulla tua Q e vedrai che "l'incredulità" è una reazione diffusa - e la A più votata ti dice "non farlo, superala", seguita da una che dice "è proprio come è". Anche se potresti non "apprezzare" le persone esperte di Python che reagiscono con stupore alle tue specifiche originali, sono piuttosto sorprendenti di per sé ;-).
Alex Martelli

30
Sì, ma sembri stupito dalla mia mancanza di conoscenza di Python ... come se fossimo una specie straniera: P
mpen

51

Non c'è modo che tu possa farlo cambiando solo quella linea. Tu puoi fare:

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

Questo crea una lista, assegna il riferimento ad a, poi anche b, usa il riferimento a per impostare il primo elemento a 2, quindi accede usando la variabile di riferimento b.


17
Questo è esattamente il tipo di incoerenza che odio su Python e questi linguaggi dinamici. (Sì sì, non è davvero "incoerente" perché stai cambiando un attributo piuttosto che il riferimento ma non mi piace ancora)
mpen

10
@Mark: in effetti. Conosco innumerevoli (beh, alcune) persone che hanno passato forse ore a cercare un "bug" nel loro codice e poi hanno scoperto che era causato da un elenco che non veniva copiato su disco.
houbysoft

14
Non ci sono incongruenze. E non ha niente a che fare con il grande dibattito statico contro dinamico. Se fossero due riferimenti alla stessa Java ArrayList, sarebbe la stessa sintassi modulo. Se usi oggetti immutabili (come le tuple), non devi preoccuparti che l'oggetto venga modificato tramite un altro riferimento.
Matthew Flaschen

L'ho usato diverse volte, più comunemente per aggirare la mancanza di 2.x di "non locale". Non è la cosa più carina da fare, ma funziona bene in un pizzico.
Glenn Maynard

1
Questo non è affatto incoerente perché l'oggetto a cui stai assegnando aed bè l'elenco, non il valore nell'elenco. Le variabili non cambiano assegnazione, l'oggetto è lo stesso oggetto. Se cambiasse, come nel caso della modifica del numero intero (ognuno dei quali è un oggetto diverso), asarebbe ora assegnato a un altro oggetto e non c'è nulla che induca ba seguirne l'esempio. Qui anon viene riassegnato, piuttosto cambia un valore all'interno dell'oggetto a cui è assegnato. Poiché bè ancora associato a quell'oggetto, rifletterà quell'oggetto e le modifiche ai valori al suo interno.
Arkigos

34

Non è un bug, è una caratteristica :-)

Quando guardi l'operatore "=" in Python, non pensare in termini di assegnazione. Non assegni le cose, le leghi. = è un operatore vincolante.

Quindi nel tuo codice, dai un nome al valore 1: a. Quindi, stai dando il valore in "a" a nome: b. Quindi si associa il valore 2 al nome "a". Il valore associato a b non cambia in questa operazione.

Provenendo da linguaggi simili al C, questo può creare confusione, ma una volta che ti sei abituato, scopri che ti aiuta a leggere e ragionare sul tuo codice in modo più chiaro: il valore che ha il nome 'b' non cambierà a meno che tu cambiarlo esplicitamente. E se fai un "import this", scoprirai che lo Zen di Python afferma che Explicit è meglio di implicit.

Si noti inoltre che anche linguaggi funzionali come Haskell utilizzano questo paradigma, con un grande valore in termini di robustezza.


39
Sai, ho letto risposte come questa dozzine di volte e non l'ho mai capito. Il comportamento di a = 1; b = a; a = 2;è esattamente lo stesso in Python, C e Java: b è 1. Perché questo focus su "= non è assegnazione, è vincolante"?
Ned Batchelder

4
Assegni le cose. Ecco perché si chiama dichiarazione di assegnazione . La distinzione che stai affermando non ha senso. E questo non ha nulla a che fare con compilato contro interpretato o statico contro dinamico. Java è un linguaggio compilato con controllo statico del tipo e non ha nemmeno puntatori.
Matthew Flaschen

3
E il C ++? "b" potrebbe essere un riferimento ad "a". Comprendere la differenza tra assegnazione e associazione è fondamentale per comprendere appieno perché Mark non può fare ciò che vorrebbe fare e come sono progettati linguaggi come Python. Concettualmente (non necessariamente nell'implementazione), "a = 1" non sovrascrive il blocco di memoria denominato "a" con 1; assegna un nome "a" all'oggetto già esistente "1", che è fondamentalmente diverso da quello che accade in C. Ecco perché i puntatori come concetto non possono esistere in Python - diventerebbero obsoleti la prossima volta che il la variabile originale era "assegnata".
Glenn Maynard,

1
@dw: mi piace questo modo di pensarci! "Rilegatura" è una buona parola. @ Ned: L'output è lo stesso, sì, ma in C il valore di "1" viene copiato in entrambi ae bmentre in Python, entrambi si riferiscono allo stesso "1" (credo). Quindi, se potessi cambiare il valore di 1 (come con gli oggetti), sarebbe diverso. Il che porta ad alcuni strani problemi di boxe / unboxing, ho sentito.
mpen

4
La differenza tra Python e C non è cosa significa "assegnazione". È ciò che significa "variabile".
dan04

28

Sì! c'è un modo per usare una variabile come puntatore in python!

Mi dispiace dover dire che molte delle risposte erano parzialmente sbagliate. In linea di principio ogni assegnazione uguale (=) condivide l'indirizzo di memoria (controlla la funzione id (obj)), ma in pratica non è tale. Ci sono variabili il cui comportamento uguale ("=") funziona nell'ultimo termine come una copia dello spazio di memoria, principalmente in oggetti semplici (ad es. Oggetto "int"), e altre in cui no (ad es. Oggetti "list", "dict") .

Ecco un esempio di assegnazione del puntatore

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

Ecco un esempio di assegnazione di 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'assegnazione del puntatore è uno strumento piuttosto utile per l'aliasing senza lo spreco di memoria aggiuntiva, in determinate situazioni per eseguire codice comodo,

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

ma bisogna essere consapevoli di questo uso per evitare errori di codice.

Per concludere, per impostazione predefinita alcune variabili sono barenames (oggetti semplici come int, float, str, ...) e alcune sono puntatori quando vengono assegnate tra di loro (ad esempio dict1 = dict2). Come riconoscerli? prova solo questo esperimento con loro. Negli IDE con il pannello di esplorazione delle variabili di solito sembra essere l'indirizzo di memoria ("@axbbbbbb ...") nella definizione degli oggetti del meccanismo di puntamento.

Suggerisco di approfondire l'argomento. Ci sono molte persone che sanno molto di più su questo argomento di sicuro. (vedi modulo "ctypes"). Spero che sia utile Buon uso degli oggetti! Saluti, José Crespo


Quindi devo usare un dizionario per passare una variabile per riferimento a una funzione e non posso passare una variabile per riferimento usando un int o una stringa?
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

Come puoi vedere ae bsono solo due nomi diversi che fanno riferimento allo stesso oggetto immutabile (int) 1. Se in seguito si scrive a = 2, si riassegna il nome aa un oggetto diverso (int) 2, ma il briferimento continua a 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)

Cosa succederebbe se invece avessi un oggetto modificabile, come un elenco [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)

Di nuovo, ci riferiamo allo stesso oggetto (elenco) [1]con due nomi diversi ae b. Comunque ora siamo in grado di mutare questa lista mentre rimane lo stesso oggetto, e a, bsaranno entrambi continueranno riferimento ad esso

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

1
Grazie per aver introdotto la funzione id. Questo risolve molti dei miei dubbi.
haudoing

12

Da un certo punto di vista, tutto è un puntatore in Python. Il tuo esempio funziona in modo molto simile al codice C ++.

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

(Un equivalente più vicino userebbe un tipo di shared_ptr<Object>invece di int*.)

Ecco un esempio: desidero che form.data ['field'] e form.field.value abbiano sempre lo stesso valore. Non è completamente necessario, ma penso che sarebbe carino.

Puoi farlo sovraccaricando __getitem__nella form.dataclasse di.


form.datanon è una classe. È necessario crearne uno o posso ignorarlo al volo? (È solo un dict di Python) Inoltre, i dati dovrebbero avere un riferimento formper accedere ai campi ... il che rende l'implementazione di questo brutto.
mpen

1

Questo è un puntatore Python (diverso da 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

Ho scritto la seguente semplice classe come, effettivamente, un modo per emulare un puntatore in 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)

Ecco un esempio di utilizzo (da una pagina del taccuino 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]

Naturalmente, è anche facile farlo funzionare per elementi di comando o attributi di un oggetto. C'è anche un modo per fare ciò che l'OP ha richiesto, usando 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)

Verrà stampato 2, seguito da 3.

Pasticciare con lo spazio dei nomi globale in questo modo è una pessima idea in modo trasparente, ma mostra che è possibile (se sconsigliabile) fare ciò che l'OP ha chiesto.

L'esempio è, ovviamente, abbastanza inutile. Ma ho trovato questa classe utile nell'applicazione per la quale l'ho sviluppata: un modello matematico il cui comportamento è governato da numerosi parametri matematici impostabili dall'utente, di diverso tipo (che, poiché dipendono da argomenti della riga di comando, non sono noti in fase di compilazione). E una volta che l'accesso a qualcosa è stato incapsulato in un oggetto Parameter, tutti questi oggetti possono essere manipolati in modo uniforme.

Sebbene non assomigli molto a un puntatore C o C ++, questo risolve un problema che avrei risolto con i puntatori se stessi scrivendo in C ++.


0

Il codice seguente emula esattamente il comportamento dei puntatori in 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    

Ecco alcuni esempi di utilizzo:

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)

Ma ora è il momento di fornire un codice più professionale, inclusa l'opzione di eliminare i puntatori, che ho appena trovato nella mia libreria personale:

# 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

Penso che tu abbia appena inscatolato i valori in un modo strano.
mpen

Intendi: un "modo intelligente" o un "modo non intelligente"?
MikeTeX

Uhhh ... Sto lottando per vedere un caso d'uso valido per una memoria globale indicizzata da un numero casuale.
mpen

Esempio di utilizzo: sono un ingegnere di algoritmi e devo lavorare con i programmatori. Lavoro con Python e loro lavorano con C ++. A volte, mi chiedono di scrivere un algoritmo per loro, e io lo scrivo il più vicino possibile al C ++ per loro comodità. I puntatori sono utili ad esempio per alberi binari, ecc.
MikeTeX

Nota: se l'archiviazione globale dà fastidio, puoi includerla come variabile globale a livello della classe stessa, che è probabilmente più elegante.
MikeTeX
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.