Pointer dengan Python?

124

Saya tahu Python tidak memiliki pointer, tetapi apakah ada cara untuk mendapatkan hasil ini 2sebagai gantinya

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

?


Berikut contohnya: Saya ingin form.data['field']dan form.field.valueselalu memiliki nilai yang sama. Ini tidak sepenuhnya diperlukan, tapi saya pikir akan menyenangkan.


Di PHP, misalnya, saya bisa melakukan ini:

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

Keluaran:

GeorgeGeorgeRalphRalph

ideone


Atau seperti ini di C ++ (menurut saya ini benar, tetapi C ++ saya berkarat):

#include <iostream>
using namespace std;

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

    return 0;
}
mpen
sumber
28
Mungkin saya dapat mengajukan pertanyaan yang mirip dengan pertanyaan S. Lott (tetapi lebih produktif): dapatkah Anda menunjukkan kepada kami beberapa kode nyata di mana Anda ingin melakukan ini? Mungkin bahkan dalam bahasa lain yang lebih sesuai dengan selera Anda? Kemungkinan masalah yang Anda coba selesaikan akan memberikan solusi yang lebih Pythonic, dan fokus pada "Saya ingin petunjuk" mengaburkan jawaban sebenarnya.
Ned Batchelder
8
Tidak membutuhkan banyak imajinasi; Saya dapat memikirkan lusinan alasan untuk ingin melakukan ini. Ini bukan cara melakukannya dalam bahasa tanpa pointer seperti Python; Anda perlu membungkusnya dalam wadah yang tidak berbeda, seperti dalam jawaban Matt.
Glenn Maynard
14
Anda tidak akan menulis fungsi swap dengan Python. Anda akan menulis a, b = b, a.
dan04
3
-1: Konstruksi dalam pertanyaan adalah (a) tidak masuk akal, dan (b) tidak ada seorang pun yang dapat memberikan contoh yang membuatnya masuk akal. Mengatakan ada "puluhan alasan" tidak sama dengan memposting contoh.
S. Lotot
1
@ Mark: Juga, saya tidak "tidak setuju". Saya bingung. Saya mengajukan pertanyaan, mencari cara untuk memahami apa ini dan mengapa menurut Anda ini sangat penting.
S. Lotot

Jawaban:

48

Saya ingin form.data['field']dan form.field.value selalu memiliki nilai yang sama

Ini layak, karena melibatkan nama-nama dihiasi dan pengindeksan - yaitu, benar-benar konstruksi berbeda dari barenames a dan bbahwa Anda bertanya tentang, dan untuk permintaan Anda sama sekali tidak mungkin. Mengapa meminta sesuatu yang tidak mungkin dan sama sekali berbeda dari (mungkin) hal yang sebenarnya Anda inginkan ?!

Mungkin Anda tidak menyadari betapa berbedanya nama bar dan nama dekorasi. Saat Anda merujuk ke barename a, Anda mendapatkan objek yang benar- abenar terikat terakhir dalam cakupan ini (atau pengecualian jika tidak terikat dalam cakupan ini) - ini adalah aspek yang dalam dan mendasar dari Python sehingga dapat tidak mungkin ditumbangkan. Saat Anda mengacu pada nama yang dihiasx.y , Anda meminta sebuah objek (objek yangx ) untuk memberikan " yatribut" - dan sebagai tanggapan atas permintaan itu, objek tersebut dapat melakukan komputasi yang sepenuhnya sewenang-wenang (dan pengindeksannya sangat mirip: itu juga memungkinkan perhitungan sewenang-wenang untuk dilakukan sebagai tanggapan).

Sekarang, contoh "desiderata aktual" Anda misterius karena dalam setiap kasus dua tingkat pengindeksan atau pengambilan atribut terlibat, sehingga kehalusan yang Anda dambakan dapat diperkenalkan dengan banyak cara. Atribut lain apa yang form.fieldseharusnya dimiliki, misalnya, selain value? Tanpa .valueperhitungan lebih lanjut , kemungkinan akan mencakup:

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

dan

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

Kehadiran .valuemenyarankan memilih formulir pertama, ditambah pembungkus yang tidak berguna:

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

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

Jika tugas seperti form.field.value = 23itu juga diharapkan untuk mengatur entri form.data, maka pembungkusnya memang harus menjadi lebih kompleks, dan tidak semuanya tidak berguna:

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)

Contoh terakhir kira-kira sedekat mungkin, dengan Python, dengan arti "penunjuk" seperti yang Anda inginkan - tetapi sangat penting untuk memahami bahwa kehalusan seperti itu hanya dapat bekerja dengan pengindeksan dan / atau nama yang dihias , tidak pernah dengan nama bar seperti yang Anda minta!

Alex Martelli
sumber
23
Saya menanyakannya seperti yang saya lakukan karena saya berharap mendapatkan solusi umum yang akan bekerja untuk semuanya sampai ke "barenames" dan saya tidak memiliki kasus tertentu dalam pikiran saat itu - hanya saja saya mengalami ini masalah dengan iterasi sebelumnya dari proyek ini dan saya tidak ingin mengalami lagi. Bagaimanapun, ini jawaban yang bagus! Namun, ketidakpercayaan itu kurang dihargai.
mpen
2
@Mark, lihat komentar di Q Anda dan Anda akan melihat bahwa "ketidakpercayaan" adalah reaksi yang tersebar luas - dan A dengan suara terbanyak memberi tahu Anda "jangan lakukan, lupakan", diikuti oleh yang berbunyi "hanya bagaimana adanya". Meskipun Anda mungkin tidak "menghargai" orang-orang berpengetahuan Python yang bereaksi dengan heran terhadap spesifikasi asli Anda, mereka sendiri cukup mencengangkan ;-).
Alex Martelli
30
Ya, tetapi Anda tampaknya heran dengan kurangnya pengetahuan Python saya ... seolah-olah kita adalah spesies asing: P
mpen
51

Tidak mungkin Anda bisa melakukan itu hanya dengan mengubah baris itu. Anda dapat melakukan:

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

Itu membuat daftar, menetapkan referensi ke a, lalu b juga, menggunakan referensi untuk menyetel elemen pertama ke 2, lalu mengakses menggunakan variabel referensi b.

Matthew Flaschen
sumber
17
Itulah jenis ketidakkonsistenan yang saya benci tentang Python dan bahasa dinamis ini. (Ya ya, itu tidak benar-benar "tidak konsisten" karena Anda mengubah atribut daripada referensi tetapi saya masih tidak menyukainya)
mpen
10
@ Mark: memang. Saya mengenal banyak sekali (yah, beberapa) orang yang menghabiskan waktu berjam-jam mencari "bug" dalam kode mereka dan kemudian menemukan bahwa itu disebabkan oleh daftar yang tidak disalin secara fisik.
houbysoft
14
Tidak ada inkonsistensi. Dan itu tidak ada hubungannya dengan debat statis v. Dinamis. Jika keduanya adalah dua referensi ke Java ArrayList yang sama, itu akan menjadi sintaks modulo yang sama. Jika Anda menggunakan objek yang tidak dapat diubah (seperti tupel), Anda tidak perlu khawatir tentang objek yang diubah melalui referensi lain.
Matthew Flaschen
Saya telah menggunakan ini beberapa kali, paling sering untuk mengatasi kurangnya 2.x "nonlokal". Ini bukan hal tercantik untuk dilakukan, tetapi berfungsi dengan baik dalam keadaan darurat.
Glenn Maynard
1
Ini sama sekali tidak tidak konsisten karena objek yang Anda tetapkan adan bmerupakan daftar, bukan nilai dalam daftar. Variabel tidak mengubah tugas, objek adalah objek yang sama. Jika itu berubah, seperti dalam kasus mengubah bilangan bulat (masing-masing adalah objek yang berbeda), asekarang akan ditugaskan ke objek lain dan tidak ada yang meminta buntuk mengikutinya. Di sini, atidak sedang ditetapkan, melainkan nilai di dalam objek yang ditetapkan berubah. Karena bmasih terikat pada objek itu, itu akan mencerminkan objek itu dan perubahan nilai di dalamnya.
arkigos
34

Ini bukan bug, ini fitur :-)

Saat Anda melihat operator '=' di Python, jangan berpikir tentang tugasnya. Anda tidak menetapkan sesuatu, Anda mengikatnya. = adalah operator yang mengikat.

Jadi dalam kode Anda, Anda memberi nilai 1 nama: a. Kemudian, Anda memberi nilai pada 'a' nama: b. Kemudian Anda mengikat nilai 2 ke nama 'a'. Nilai yang terikat ke b tidak berubah dalam operasi ini.

Berasal dari bahasa mirip C, ini bisa membingungkan, tetapi setelah Anda terbiasa, Anda akan terbantu untuk membaca dan bernalar tentang kode Anda dengan lebih jelas: nilai yang memiliki nama 'b' tidak akan berubah kecuali Anda mengubahnya secara eksplisit. Dan jika Anda melakukan 'impor ini', Anda akan menemukan bahwa Zen of Python menyatakan bahwa Eksplisit lebih baik daripada implisit.

Perhatikan juga bahwa bahasa fungsional seperti Haskell juga menggunakan paradigma ini, dengan nilai yang tinggi dalam hal ketahanan.

David Harks
sumber
39
Anda tahu, saya telah membaca jawaban seperti ini puluhan kali, dan saya tidak pernah memahaminya. Perilaku a = 1; b = a; a = 2;persis sama di Python, C, dan Java: b adalah 1. Mengapa fokus pada "= bukan tugas, ini mengikat"?
Ned Batchelder
4
Anda melakukan tugas. Itulah mengapa ini disebut pernyataan penugasan . Perbedaan yang Anda nyatakan tidak masuk akal. Dan ini tidak ada hubungannya dengan kompilasi v. Interpreted atau static v. Dynamic. Java adalah bahasa yang dikompilasi dengan pemeriksaan tipe statis, dan juga tidak memiliki petunjuk.
Matthew Flaschen
3
Bagaimana dengan C ++? "b" mungkin mengacu pada "a". Memahami perbedaan antara penugasan dan pengikatan sangat penting untuk memahami sepenuhnya mengapa Mark tidak dapat melakukan apa yang ingin dia lakukan, dan bagaimana bahasa seperti Python dirancang. Secara konseptual (tidak harus dalam implementasi), "a = 1" tidak menimpa blok memori bernama "a" dengan 1; itu memberi nama "a" ke objek "1" yang sudah ada, yang pada dasarnya berbeda dari apa yang terjadi di C. Itulah mengapa pointer sebagai konsep tidak bisa ada di Python - mereka akan menjadi basi saat berikutnya variabel asli telah "dialihkan".
Glenn Maynard
1
@dw: Saya suka cara berpikir seperti ini! "Mengikat" adalah kata yang bagus. @Ned: Outputnya sama, ya, tetapi di C nilai "1" disalin ke keduanya adan bsedangkan di Python, keduanya merujuk ke "1" yang sama (menurut saya). Jadi, jika Anda bisa mengubah nilai 1 (seperti objek), itu akan berbeda. Yang menyebabkan beberapa masalah tinju / unboxing yang aneh, saya dengar.
mpen
4
Perbedaan antara Python dan C bukanlah apa artinya "tugas". Itu yang dimaksud dengan "variabel".
dan04
28

Iya! ada cara untuk menggunakan variabel sebagai penunjuk di python!

Saya menyesal mengatakan bahwa banyak dari jawaban yang salah sebagian. Pada prinsipnya setiap penugasan yang sama (=) berbagi alamat memori (periksa fungsi id (obj)), tetapi dalam praktiknya tidak demikian. Ada variabel yang tingkah lakunya sama ("=") berfungsi di istilah terakhir sebagai salinan ruang memori, sebagian besar dalam objek sederhana (misalnya objek "int"), dan variabel lain yang tidak (misalnya objek "list", "dict") .

Berikut adalah contoh penetapan penunjuk

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

Berikut adalah contoh tugas menyalin

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

Penugasan penunjuk adalah alat yang sangat berguna untuk aliasing tanpa membuang memori ekstra, dalam situasi tertentu untuk melakukan kode yang nyaman,

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

tetapi kita harus menyadari penggunaan ini untuk mencegah kesalahan kode.

Sebagai kesimpulan, secara default beberapa variabel adalah barenames (objek sederhana seperti int, float, str, ...), dan beberapa adalah pointer ketika ditugaskan di antara mereka (misalnya dict1 = dict2). Bagaimana cara mengenalinya? coba saja eksperimen ini dengan mereka. Dalam IDE dengan panel explorer variabel biasanya muncul sebagai alamat memori ("@axbbbbbb ...") dalam definisi objek mekanisme penunjuk.

Saya sarankan menyelidiki topik tersebut. Ada banyak orang yang tahu lebih banyak tentang topik ini dengan pasti. (lihat modul "ctypes"). Saya harap ini membantu. Nikmati penggunaan objek dengan baik! Hormat kami, José Crespo

José Crespo Barrios
sumber
Jadi saya harus menggunakan kamus untuk melewatkan variabel dengan mengacu pada suatu fungsi, dan saya tidak bisa melewatkan variabel dengan referensi menggunakan int atau string?
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

Seperti yang Anda lihat adan bhanya dua nama berbeda yang merujuk ke objek tetap yang sama (int) 1. Jika nanti Anda menulis a = 2, Anda menetapkan ulang nama ake objek yang berbeda (int) 2, tetapi bterus merujuk ke 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)

Apa yang akan terjadi jika Anda memiliki objek yang bisa berubah, seperti daftar [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)

Sekali lagi, kami merujuk ke objek yang sama (daftar) [1]dengan dua nama berbeda adan b. Namun sekarang kita dapat mengubah daftar ini sementara itu tetap menjadi objek yang sama, dan a, bkeduanya akan terus merujuk padanya

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # same as before
>> id(b)
328817800  # same as before
Andreas K.
sumber
1
Terima kasih telah memperkenalkan fungsi id. Ini memecahkan banyak keraguan saya.
haudoing
12

Dari satu sudut pandang, semuanya adalah penunjuk dengan Python. Contoh Anda berfungsi sangat mirip dengan kode C ++.

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

(Persamaan yang lebih dekat akan menggunakan beberapa jenis shared_ptr<Object>alih-alih int*.)

Berikut adalah contohnya: Saya ingin form.data ['field'] dan form.field.value selalu memiliki nilai yang sama. Ini tidak sepenuhnya diperlukan, tapi menurut saya akan menyenangkan.

Anda dapat melakukan ini dengan membebani __getitem__di form.datakelas.

dan04
sumber
form.databukan kelas. Apakah perlu membuatnya menjadi satu, atau dapatkah saya menggantinya dengan cepat? (Ini hanya dikt python) Selain itu, data harus memiliki referensi kembali formuntuk mengakses bidang ... yang membuat penerapan ini jelek.
mpen
1

Ini adalah penunjuk python (berbeda dari 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
Henry
sumber
0

Saya menulis kelas sederhana berikut sebagai, secara efektif, cara untuk meniru pointer dengan 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)

Berikut adalah contoh penggunaan (dari halaman buku catatan 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]

Tentu saja, ini juga mudah untuk membuat ini berfungsi untuk item atau atribut suatu objek. Bahkan ada cara untuk melakukan apa yang diminta OP, menggunakan 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)

Ini akan mencetak 2, diikuti oleh 3.

Mengacaukan namespace global dengan cara ini jelas merupakan ide yang buruk, tetapi ini menunjukkan bahwa adalah mungkin (jika tidak disarankan) untuk melakukan apa yang diminta OP.

Contohnya, tentu saja, tidak ada gunanya. Tetapi saya telah menemukan kelas ini berguna dalam aplikasi yang saya kembangkan: model matematika yang perilakunya diatur oleh banyak parameter matematika yang dapat diatur pengguna, dari beragam jenis (yang, karena bergantung pada argumen baris perintah, tidak diketahui pada waktu kompilasi). Dan begitu akses ke sesuatu telah dienkapsulasi dalam objek Parameter, semua objek tersebut dapat dimanipulasi dengan cara yang seragam.

Meskipun tidak terlihat seperti pointer C atau C ++, ini memecahkan masalah yang akan saya selesaikan dengan pointer jika saya menulis dalam C ++.

Leon Avery
sumber
0

Kode berikut mengemulasi dengan tepat perilaku pointer di 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    

Berikut adalah contoh penggunaan:

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)

Tetapi sekarang saatnya memberikan kode yang lebih profesional, termasuk opsi untuk menghapus pointer, yang baru saja saya temukan di perpustakaan pribadi saya:

# 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
sumber
Saya pikir Anda baru saja mengemas nilai dengan cara yang aneh.
mpen
Maksud Anda: "cara yang cerdas" atau "cara yang tidak pintar"?
MikeTeX
Uhhh ... Saya kesulitan untuk melihat kasus penggunaan yang valid untuk penyimpanan global yang diindeks oleh nomor acak.
mpen
Contoh penggunaan: Saya seorang insinyur algoritma dan saya harus bekerja dengan programmer. Saya bekerja dengan Python dan mereka bekerja dengan C ++. Terkadang, mereka meminta saya untuk menulis algoritme untuk mereka, dan saya menulisnya sedekat mungkin dengan C ++ untuk kenyamanan mereka. Pointer berguna misalnya untuk pohon biner dll.
MikeTeX
Catatan: jika penyimpanan global mengganggu, Anda dapat memasukkannya sebagai variabel global pada tingkat kelas itu sendiri, yang mungkin lebih elegan.
MikeTeX