Wskaźniki w Pythonie?

124

Wiem, Python nie ma wskazówek, ale czy jest jakiś sposób, aby ten plon 2zamiast

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

?


Oto przykład: chcę form.data['field']i form.field.valuezawsze mieć tę samą wartość. Nie jest to całkowicie konieczne, ale myślę, że byłoby miło.


Na przykład w PHP mogę to zrobić:

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

Wynik:

GeorgeGeorgeRalphRalph

ideone


Lub tak w C ++ (myślę, że to prawda, ale mój C ++ jest zardzewiały):

#include <iostream>
using namespace std;

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

    return 0;
}
mpen
źródło
28
Być może mogę zadać pytanie podobne do S. Lotta (ale bardziej produktywne): czy możesz pokazać nam prawdziwy kod, w którym chciałeś to zrobić? Może nawet w innym języku, który bardziej Ci odpowiada? Jest prawdopodobne, że problem, który próbujesz rozwiązać, będzie pasował do rozwiązania bardziej Pythonowego, a skupienie się na „Chcę wskaźników” przesłania prawdziwą odpowiedź.
Ned Batchelder
8
To nie wymaga dużej wyobraźni; Przychodzą mi do głowy dziesiątki powodów, dla których warto to zrobić. Po prostu nie jest to, jak to się robi w językach bez wskaźników, takich jak Python; musisz owinąć go w pojemnik, który nie jest niezmienny, jak w odpowiedzi Matta.
Glenn Maynard
14
Nie napisałbyś funkcji wymiany w Pythonie. Ty byś pisał a, b = b, a.
dan04,
3
-1: Konstrukcja w pytaniu jest (a) bezsensowna i (b) nikt nie wydaje się być w stanie podać przykładu, który czyni go sensownym. Stwierdzenie, że istnieją „dziesiątki powodów”, to nie to samo, co opublikowanie przykładu.
S.Lott
1
@Mark: Poza tym nie "nie zgadzam się". Jestem zmieszany. Zadaję pytanie, szukając sposobu, aby zrozumieć, co to jest i dlaczego uważasz, że to takie ważne.
S.Lott,

Odpowiedzi:

48

Chcę form.data['field']i form.field.valuezawsze mieć tę samą wartość

Jest to możliwe, ponieważ wiąże urządzone nazwiska i indeksowanie - tzn zupełnie różne konstrukcje od barenames a i bże pytasz o, a ze swoim życzeniem jest zupełnie niemożliwe. Po co prosić o coś niemożliwego i całkowicie różniącego się od (możliwej) rzeczy, której naprawdę chcesz ?!

Może nie zdajesz sobie sprawy, jak drastycznie różne są barenames i zdobione nazwy. Kiedy odnosisz się do barename a, otrzymujesz dokładnie ten obiekt, z którym abył ostatnio związany w tym zakresie (lub wyjątek, jeśli nie był związany w tym zakresie) - jest to tak głęboki i fundamentalny aspekt Pythona, że ​​może nie może być obalony. Kiedy odnosisz się do ozdobionej nazwy x.y, prosisz obiekt (obiekt się xodnosi) o podanie „ yatrybutu” - w odpowiedzi na to żądanie obiekt może wykonać całkowicie dowolne obliczenia (a indeksowanie jest dość podobne: umożliwia również wykonanie dowolnych obliczeń w odpowiedzi).

Twój przykład „rzeczywistych dezyderatów” jest tajemniczy, ponieważ w każdym przypadku występują dwa poziomy indeksowania lub uzyskiwania atrybutów, więc subtelność, której pragniesz, może zostać wprowadzona na wiele sposobów. Jakie inne atrybuty form.fieldmają mieć na przykład na przykład value? Bez tych dalszych .valueobliczeń możliwości obejmowałyby:

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

i

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

Obecność .valuesugeruje wybranie pierwszej formy plus rodzaj bezużytecznego opakowania:

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

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

Jeśli przypisania takie form.field.value = 23mają również ustawiać wpis form.data, to opakowanie musi stać się rzeczywiście bardziej złożone, a nie aż tak bezużyteczne:

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)

Ten ostatni przykład jest mniej więcej tak zbliżony, jak to tylko możliwe, w Pythonie, do znaczenia „wskaźnika”, jak chcesz - ale ważne jest, aby zrozumieć, że takie subtelności mogą zawsze działać tylko z indeksowaniem i / lub dekorowanymi nazwami , nigdy z barenames, jak pierwotnie prosiłeś!

Alex Martelli
źródło
23
Zapytałem to tak, jak to zrobiłem, ponieważ miałem nadzieję uzyskać ogólne rozwiązanie, które będzie działać na wszystko, aż do „barenames” i nie miałem wtedy na myśli żadnego konkretnego przypadku - tylko, że natknąłem się na to problem z poprzednią iteracją tego projektu i nie chciałem ponownie napotkać. W każdym razie to świetna odpowiedź! Jednak niedowierzanie jest mniej doceniane.
mpen
2
@Mark, spójrz na komentarze do swojego Q, a zobaczysz, że „niedowierzanie” to powszechna reakcja - a najwyżej głosowane A mówi Ci „nie rób tego, daj sobie spokój”, a następnie mówi: „tak właśnie jest”. Chociaż możesz nie "doceniać" ludzi znających Pythona, którzy reagują ze zdziwieniem na twoje oryginalne specyfikacje, oni sami w sobie zadziwiający ;-).
Alex Martelli
30
Tak, ale wydajesz się zdziwiony moim brakiem znajomości Pythona ... jakbyśmy byli obcym gatunkiem: P
mpen
51

Nie da się tego zrobić, zmieniając tylko tę linię. Możesz to zrobić:

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

To tworzy listę, przypisuje odniesienie do a, następnie b również, używa odwołania do ustawienia pierwszego elementu na 2, a następnie uzyskuje dostęp za pomocą zmiennej odniesienia b.

Matthew Flaschen
źródło
17
Właśnie tego rodzaju niespójności nienawidzę w Pythonie i tych dynamicznych językach. (Tak, tak, to nie jest „niespójne”, ponieważ zmieniasz atrybut, a nie odniesienie, ale nadal mi się to nie podoba)
mpen
10
@Mark: rzeczywiście. Znam niezliczone (no, kilka) ludzi, którzy spędzili prawdopodobnie godziny szukając „błędu” w swoim kodzie, a następnie stwierdzając, że jest to spowodowane tym, że lista nie została skopiowana na stałe.
houbysoft
14
Nie ma niespójności. I nie ma to nic wspólnego z wielką statyczną i dynamiczną debatą. Gdyby były to dwa odwołania do tej samej tablicy ArrayList języka Java, byłaby to ta sama składnia modulo. Jeśli używasz niezmiennych obiektów (takich jak krotki), nie musisz się martwić, że obiekt zostanie zmieniony przez inne odwołanie.
Matthew Flaschen
Używałem tego kilka razy, najczęściej w celu obejścia braku „nielokalnego” w wersji 2.x. Nie jest to najpiękniejsza rzecz do zrobienia, ale działa dobrze w mgnieniu oka.
Glenn Maynard,
1
To nie jest niespójne w ogóle, ponieważ obiekt jesteś przypisując ai blista, a nie wartość na liście. Zmienne nie zmieniają przypisania, obiekt jest tym samym obiektem. Gdyby się zmienił, jak w przypadku zmiany liczby całkowitej (z których każdy jest innym obiektem), azostałby teraz przypisany do innego obiektu i nic nie skłania bdo pójścia w jego ślady. Tutaj anie jest ponownie przypisywana, a raczej zmienia się wartość wewnątrz obiektu, do którego jest przypisana. Ponieważ bjest nadal powiązany z tym obiektem, będzie odzwierciedlał ten obiekt i zmiany wartości w nim.
arkigos
34

To nie jest błąd, to jest funkcja :-)

Patrząc na operator „=” w Pythonie, nie myśl w kategoriach przypisania. Nie przypisujesz rzeczy, wiążesz je. = jest operatorem wiążącym.

Zatem w swoim kodzie nadajesz wartości 1 nazwę: a. Następnie podajesz wartość w „a” nazwę: b. Następnie wiążesz wartość 2 z nazwą „a”. Wartość przypisana do b nie zmienia się w tej operacji.

Pochodząc z języków podobnych do języka C, może to być mylące, ale gdy się do tego przyzwyczaisz, okaże się, że pomaga to w jaśniejszym czytaniu i uzasadnianiu kodu: wartość, która ma nazwę `` b '', nie zmieni się, chyba że wyraźnie to zmienić. A jeśli zrobisz „zaimportuj to”, przekonasz się, że Zen of Python stwierdza, że ​​Explicit jest lepsze niż implicit.

Zauważ również, że języki funkcjonalne, takie jak Haskell, również używają tego paradygmatu, z wielką wartością pod względem niezawodności.

David Harks
źródło
39
Wiesz, czytałem takie odpowiedzi dziesiątki razy i nigdy ich nie rozumiałem. Zachowanie a = 1; b = a; a = 2;jest dokładnie takie samo w Pythonie, C i Javie: b wynosi 1. Dlaczego skupienie się na "= nie jest przypisaniem, tylko wiąże"?
Ned Batchelder
4
Przypisujesz rzeczy. Dlatego nazywa się to instrukcją przypisania . Rozróżnienie, które podajesz, nie ma sensu. I nie ma to nic wspólnego ze skompilowanym v. Zinterpretowanym lub statycznym v. Dynamicznym. Java jest językiem kompilowanym ze statycznym sprawdzaniem typów i nie ma też wskaźników.
Matthew Flaschen
3
A co z C ++? „b” może być odniesieniem do „a”. Zrozumienie różnicy między przypisaniem a wiązaniem ma kluczowe znaczenie dla pełnego zrozumienia, dlaczego Mark nie może robić tego, co chciałby, i jak są zaprojektowane języki takie jak Python. Koncepcyjnie (niekoniecznie w implementacji) „a = 1” nie zastępuje bloku pamięci o nazwie „a” wartością 1; przypisuje nazwę „a” już istniejącemu obiektowi „1”, która jest zasadniczo inna niż to, co dzieje się w C. Dlatego wskaźniki jako koncepcja nie mogą istnieć w Pythonie - stałyby się nieaktualne, gdy następnym razem oryginalna zmienna została „przypisana”.
Glenn Maynard
1
@dw: Podoba mi się ten sposób myślenia! „Wiązanie” to dobre słowo. @Ned: Wyjście jest takie samo, tak, ale w C wartość „1” jest kopiowana do obu, aa bpodczas gdy w Pythonie oba odnoszą się do tego samego „1” (tak mi się wydaje). Tak więc, gdybyś mógł zmienić wartość 1 (tak jak w przypadku obiektów), byłoby inaczej. Słyszę, że prowadzi to do dziwnych problemów z boksowaniem / rozpakowywaniem.
mpen
4
Różnica między Pythonem a C nie polega na tym, co oznacza „przypisanie”. To właśnie oznacza „zmienna”.
dan04
28

Tak! istnieje sposób na użycie zmiennej jako wskaźnika w Pythonie!

Z przykrością stwierdzam, że wiele odpowiedzi było częściowo błędnych. W zasadzie każde przypisanie równości (=) dzieli adres pamięci (sprawdź funkcję id (obj)), ale w praktyce tak nie jest. Istnieją zmienne, których równe zachowanie („=”) działa w ostatnim terminie jako kopia przestrzeni pamięci, głównie w prostych obiektach (np. Obiekt „int”), i inne, w których nie (np. Obiekty „lista”, „dykt”) .

Oto przykład przypisania wskaźnika

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

Oto przykład przypisania kopii

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

Przypisanie wskaźnika to całkiem przydatne narzędzie do aliasingu bez marnowania dodatkowej pamięci, w pewnych sytuacjach do wykonywania wygodnego kodu,

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

ale trzeba być świadomym tego zastosowania, aby uniknąć błędów w kodzie.

Podsumowując, domyślnie niektóre zmienne to barenames (proste obiekty, takie jak int, float, str, ...), a niektóre są wskaźnikami, gdy są przypisane między nimi (np. Dict1 = dict2). Jak je rozpoznać? po prostu wypróbuj z nimi ten eksperyment. W środowiskach IDE z panelem eksploratora zmiennych zwykle pojawia się adres pamięci („@axbbbbbb…”) w definicji obiektów mechanizmu wskaźnika.

Proponuję zbadać w temacie. Na pewno jest wiele osób, które na ten temat wiedzą znacznie więcej. (zobacz moduł "ctypes"). Mam nadzieję, że jest to pomocne. Ciesz się dobrym użytkowaniem obiektów! Pozdrawiam, José Crespo

José Crespo Barrios
źródło
Więc muszę użyć słownika, aby przekazać zmienną przez odwołanie do funkcji, a nie mogę przekazać zmiennej przez odwołanie przy użyciu int lub ciągu?
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

Jak widać ai bsą to tylko dwie różne nazwy, które odnoszą się do tego samego niezmiennego obiektu (int) 1. Jeśli później napiszesz a = 2, ponownie przypiszesz nazwę ado innego obiektu (int) 2, ale bkontynuacja odwołuje się do 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)

Co by się stało, gdybyś zamiast tego miał zmienny obiekt, taki jak lista [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)

Ponownie odwołujemy się do tego samego obiektu (listy) za [1]pomocą dwóch różnych nazwa i b. Jednak teraz możemy mutować tę listę, podczas gdy pozostaje ten sam obiekt, a a, bbędzie w dalszym ciągu zarówno odniesienie do niego

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # same as before
>> id(b)
328817800  # same as before
Andreas K.
źródło
1
Dziękujemy za wprowadzenie funkcji id. To rozwiązuje wiele moich wątpliwości.
Haudoing
12

Z jednego punktu widzenia w Pythonie wszystko jest wskaźnikiem. Twój przykład działa podobnie jak kod C ++.

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

(Bliższy odpowiednik użyłby jakiegoś typu shared_ptr<Object>zamiast int*.)

Oto przykład: chcę, aby form.data ['field'] i form.field.value zawsze miały tę samą wartość. Nie jest to całkowicie konieczne, ale myślę, że byłoby miło.

Można to zrobić przez przeciążenie __getitem__w form.data„s klasy.

dan04
źródło
form.datanie jest klasą. Czy trzeba to zrobić, czy mogę go zastąpić w locie? (To tylko dyktowanie Pythona) Ponadto dane musiałyby mieć odniesienie z powrotem, formaby uzyskać dostęp do pól ... co sprawia, że ​​implementacja jest brzydka.
mpen
1

To jest wskaźnik Pythona (inny niż 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
Henz
źródło
0

Napisałem następującą prostą klasę jako skuteczny sposób na emulację wskaźnika w Pythonie:

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)

Oto przykład użycia (ze strony notatnika 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]

Oczywiście łatwo jest również wykonać tę pracę dla dyktowanych elementów lub atrybutów obiektu. Jest nawet sposób na zrobienie tego, o co prosił OP, używając 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)

Spowoduje to wydrukowanie 2, a następnie 3.

Majstrowanie w ten sposób z globalną przestrzenią nazw jest w pewnym sensie okropnym pomysłem, ale pokazuje, że można (jeśli jest to niewskazane) zrobić to, o co prosił OP.

Przykład jest oczywiście dość bezcelowy. Okazało się jednak, że ta klasa jest przydatna w aplikacji, dla której ją opracowałem: model matematyczny, którego zachowanie jest regulowane przez wiele ustawianych przez użytkownika parametrów matematycznych, różnego typu (które, ponieważ zależą od argumentów wiersza poleceń, nie są znane w czasie kompilacji). A gdy dostęp do czegoś zostanie zamknięty w obiekcie Parameter, wszystkimi takimi obiektami można manipulować w jednolity sposób.

Chociaż nie wygląda jak wskaźnik C lub C ++, jest to rozwiązanie problemu, który rozwiązałbym za pomocą wskaźników, gdybym pisał w C ++.

Leon Avery
źródło
0

Poniższy kod dokładnie emuluje zachowanie wskaźników w 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    

Oto przykłady użycia:

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)

Ale teraz nadszedł czas, aby podać bardziej profesjonalny kod, w tym opcję usuwania wskaźników, które właśnie znalazłem w mojej osobistej bibliotece:

# 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
MikeTeX
źródło
Myślę, że właśnie w dziwny sposób ujęłeś wartości.
otwarty
Masz na myśli: „sprytny sposób” czy „niezbyt mądry sposób”?
MikeTeX
Uhhh ... Staram się znaleźć prawidłowy przypadek użycia dla globalnej pamięci indeksowanej według losowej liczby.
mpen
Przykład użycia: Jestem inżynierem algorytmów i muszę współpracować z programistami. Pracuję z Pythonem, a oni pracują z C ++. Czasami proszą mnie o napisanie dla nich algorytmu i piszę go jak najbliżej C ++ dla ich wygody. Wskaźniki są przydatne np. W przypadku drzew binarnych itp.
MikeTeX
Uwaga: jeśli magazyn globalny przeszkadza, możesz dołączyć go jako zmienną globalną na poziomie samej klasy, co jest prawdopodobnie bardziej eleganckie.
MikeTeX