Python'da işaretçiler?


124

Python'un işaretçileri olmadığını biliyorum, ancak 2bunun yerine bu verimi almanın bir yolu var mı

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

?


İşte bir örnek: Ben form.data['field']ve form.field.valueher zaman aynı değere sahip olmak istiyorum . Tamamen gerekli değil ama güzel olacağını düşünüyorum.


Örneğin PHP'de şunu yapabilirim:

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

Çıktı:

GeorgeGeorgeRalphRalph

ideone


Veya C ++ 'da olduğu gibi (Bunun doğru olduğunu düşünüyorum, ancak C ++ programım paslı)

#include <iostream>
using namespace std;

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

    return 0;
}

28
Belki S.Lott'a benzer bir soru sorabilirim (ama daha üretken): Bunu yapmak istediğiniz yerde bize gerçek bir kod gösterebilir misiniz? Belki zevkinize daha uygun başka bir dilde bile? Çözmeye çalıştığınız problem muhtemelen daha Pythonic çözüme katkıda bulunacak ve "işaretçiler istiyorum" a odaklanmak gerçek yanıtı belirsizleştiriyor.
Ned Batchelder

8
Fazla hayal gücü gerektirmez; Bunu yapmak için düzinelerce neden düşünebilirim. Python gibi işaretçi olmayan dillerde böyle yapılmıyor; Matt'in cevabında olduğu gibi, onu değişmeyen bir kaba sarmanız gerekir.
Glenn Maynard

14
Python'da bir takas işlevi yazmazsınız. Sen yazarsın a, b = b, a.
dan04

3
-1: Sorudaki yapı (a) anlamsız ve (b) hiç kimse onu mantıklı kılan bir örnek sunamıyor gibi görünüyor. "Düzinelerce neden" olduğunu söylemek, örnek vermekle aynı şey değildir.
S.Lott

1
@Mark: Ayrıca "katılmıyorum". Kafam karıştı. Bir soru soruyorum, bunun ne olduğunu ve neden bu kadar önemli olduğunu düşündüğünüzü anlamanın bir yolunu arıyorum.
S.Lott

Yanıtlar:


48

İstediğim form.data['field']ve form.field.valuehep aynı değere sahip

Bu mümkündür, çünkü süslü isimler ve indeksleme içerir - yani, baren isimlerinden tamamen farklı yapılar ve sorduğunuz ve isteğinizle tamamen imkansızdır. Neden imkansız ve gerçekten istediğiniz (mümkün) şeyden tamamen farklı bir şey istesiniz ? ab

Belki de baren adlarının ve süslü isimlerin ne kadar büyük ölçüde farklı olduğunu anlamıyorsunuz. Bir barename'e atıfta bulunduğunuzda a, abu kapsamda en son bağlanılan nesneyi (veya bu kapsamda bağlı değilse bir istisna) tam olarak alıyorsunuz - bu, Python'un yapabileceği kadar derin ve temel bir yönüdür Muhtemelen altüst edilemez. Bir söz ettiğimizde dekore isim x.y, bir nesneyi soruyorsun (nesne xkaynağı "memnun etmek anlamına gelir) yniteliği" - ve bu talebe yanıt olarak, nesne tamamen keyfi hesaplamalar gerçekleştirebilir (ve indeksleme oldukça benzer: ayrıca isteğe bağlı hesaplamaların yanıt olarak gerçekleştirilmesine de izin verir).

Şimdi, "gerçek arzu edilen veri" örneğiniz gizemlidir çünkü her durumda iki seviye indeksleme veya nitelik alma söz konusudur, bu nedenle arzuladığınız incelik birçok şekilde tanıtılabilir. form.fieldÖrneğin, başka hangi niteliklere sahip olduğu varsayılır value? Daha fazla .valuehesaplama olmadan , olasılıklar şunları içerecektir:

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

ve

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

Varlığı .value, ilk formu ve bir tür yararsız paketleyiciyi seçmeyi öneriyor:

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

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

Bu tür atamalarınform.field.value = 23 da girişi ayarlaması gerekiyorsa form.data, sarmalayıcı gerçekten daha karmaşık hale gelmeli ve o kadar da yararsız olmamalıdır:

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)

İkinci örnek, Python'da istediğiniz gibi göründüğü kadarıyla "işaretçi" duygusuna kabaca olabildiğince yakındır - ancak bu tür inceliklerin yalnızca indeksleme ve / veya süslenmiş adlarla çalışabileceğini anlamak çok önemlidir , asla Başlangıçta sorduğunuz gibi baren adlarıyla!


23
Bunu yaptığım gibi sordum çünkü "barenames" e kadar her şey için işe yarayacak genel bir çözüm bulmayı umuyordum ve o sırada aklımda belirli bir vaka yoktu - sadece bununla karşılaşmıştım. Bu projenin önceki yinelemesiyle ilgili sorun ve tekrar karşılaşmak istemedim. Her neyse, bu harika bir cevap! Bununla birlikte, şüphe daha az takdir edilmektedir.
mpen

2
@Mark, Q'nuzdaki yorumlara bakın ve "inanmazlığın" yaygın bir tepki olduğunu göreceksiniz - ve en çok oy alan A size "yapma, üstesinden gelin" diyor, ardından bir tane "olduğu gibi" diyor. Orijinal gözlük hayretle tepkimeye Python bilgili insanlar "takdir" olmasa da, bunlar şunlardır ;-) kendileri oldukça şaşırtıcı.
Alex Martelli

30
Evet, ama Python
bilgimden

51

Bunu sadece bu satırı değiştirerek yapmanın bir yolu yok. Yapabilirsin:

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

Bu, bir liste oluşturur, referansı a'ya atar ve ardından b, ilk öğeyi 2'ye ayarlamak için a referansını kullanır, ardından b referans değişkenini kullanarak erişir.


17
İşte Python ve bu dinamik diller hakkında nefret ettiğim türden bir tutarsızlık. (Evet, evet, bu gerçekten "tutarsız" değil çünkü referanstan ziyade bir niteliği değiştiriyorsunuz ama yine de hoşuma
gitmiyor

10
@Mark: gerçekten. Muhtemelen saatlerini kodlarında bir "hata" arayarak geçiren ve sonra da bunun zor kopyalanmayan bir listeden kaynaklandığını bulan sayısız (birkaç tane) insan tanıyorum.
houbysoft

14
Tutarsızlık yok. Ve bunun büyük statik karşı dinamik tartışmasıyla hiçbir ilgisi yok. Aynı Java ArrayList'e iki başvuru olsaydı, aynı, modulo sözdizimi olurdu. Değişmez nesneler (tuple gibi) kullanırsanız, başka bir referansla değiştirilen nesne hakkında endişelenmenize gerek yoktur.
Matthew Flaschen

Bunu birkaç kez kullandım, en çok 2.x'in "yerel olmayan" eksikliğini gidermek için. Yapılacak en güzel şey değil, ama bir tutamda iyi çalışıyor.
Glenn Maynard

1
Eğer atadığınız nesne çünkü bu hiç de tutarsız değildir ave bliste, listede bulunmayan bir değerdir. Değişkenler atamayı değiştirmiyor, nesne aynı nesnedir. Tamsayının değiştirilmesi durumunda olduğu gibi (her biri farklı nesnelerdir) değiştiyse, aşimdi başka bir nesneye atanacak ve bbunu takip edecek hiçbir şey olmayacaktır. Burada ayeniden atanmıyor, atandığı nesnenin içindeki bir değer değişiyor. Yana bhala o nesneye bağlı, o nesneyi ve içindeki değerlere değişiklikleri yansıtır.
arkigos

34

Bu bir hata değil, bir özelliktir :-)

Python'da '=' operatörüne baktığınızda, atama açısından düşünmeyin. Bir şeyleri atamazsın, bağlarsın. = bir bağlama operatörüdür.

Yani kodunuzda 1 değerine bir ad veriyorsunuz: a. Ardından, 'a' daki değeri bir ad veriyorsunuz: b. Ardından 2 değerini 'a' adına bağlarsınız. B'ye bağlanan değer bu işlemde değişmez.

C benzeri dillerden gelince, bu kafa karıştırıcı olabilir, ancak buna alıştıktan sonra, kodunuzu daha net bir şekilde okumanıza ve muhakeme etmenize yardımcı olduğunu fark edersiniz: 'b' adını taşıyan değer, siz açıkça değiştirin. Ve bir 'bunu içe aktar' yaparsanız, Python'un Zen'sinin Explicit'in örtükten daha iyi olduğunu belirttiğini göreceksiniz.

Haskell gibi işlevsel dillerin de bu paradigmayı, sağlamlık açısından büyük bir değerle kullandığını unutmayın.


39
Biliyor musun, bunun gibi cevapları defalarca okudum ve hiç anlamadım. Davranışı a = 1; b = a; a = 2;Python, C ve Java'da tamamen aynıdır: b 1'dir. Neden bu odak "= atama değil, bağlayıcıdır"?
Ned Batchelder

4
Bir şeyler atarsın. Bu yüzden buna atama ifadesi deniyor . Belirttiğiniz ayrım mantıklı değil. Ve bunun derlenmiş v. Yorumlanan veya statik v. Dinamik ile ilgisi yoktur. Java, statik yazım denetimine sahip derlenmiş bir dildir ve işaretçileri de yoktur.
Matthew Flaschen

3
C ++ ne olacak? "b", "a" ya referans olabilir. Atama ve ciltleme arasındaki farkı anlamak, Mark'ın yapmak istediği şeyi neden yapamadığını ve Python gibi dillerin nasıl tasarlandığını tam olarak anlamak için çok önemlidir. Kavramsal olarak (uygulamada olması gerekmez), "a = 1" 1 ile "a" adlı bellek bloğunun üzerine yazmaz; zaten var olan "1" nesnesine bir "a" adı atar, bu C'de olandan temelde farklıdır. Bu nedenle, Python'da bir kavram olarak işaretçiler var olamaz - bir dahaki sefere orijinal değişken "üzerine atandı".
Glenn Maynard

1
@dw: Bu şekilde düşünmeyi seviyorum! "Bağlamak" iyi bir kelimedir. @Ned: çıktı evet aynıdır, ancak C "1" değeri hem kopyalanır ave bPython oysa ikisi de atıfta için aynı "1" (Sanırım). Dolayısıyla, 1'in değerini değiştirebilseydiniz (nesnelerde olduğu gibi), bu farklı olacaktır. Bu da bazı garip boks / kutudan çıkarma sorunlarına yol açıyor, duydum.
mpen

4
Python ve C arasındaki fark, "atama" nın ne anlama geldiği değildir. "Değişken" ne anlama geliyor.
dan04

28

Evet! Bir değişkeni python'da işaretçi olarak kullanmanın bir yolu var!

Pek çok yanıtın kısmen yanlış olduğunu söylediğim için üzgünüm. Prensipte her eşit (=) atama bellek adresini paylaşır (id (obj) işlevini kontrol edin), ancak pratikte böyle değildir. Eşit ("=") davranışı son dönemde bellek alanının bir kopyası olarak çalışan, çoğunlukla basit nesnelerde (ör. "İnt" nesne) ve diğerlerinde olmayan (ör. "Liste", "dikte" nesneler) değişkenler vardır. .

İşte bir işaretçi ataması örneği

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

İşte bir kopya atama örneği

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

İşaretçi atama, bazı durumlarda rahat kod gerçekleştirmek için ekstra bellek israfı olmadan takma adlandırma için oldukça kullanışlı bir araçtır,

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

ancak kod hatalarını önlemek için bu kullanımın farkında olunması gerekir.

Sonuç olarak, varsayılan olarak bazı değişkenler baren isimleridir (int, float, str, ... gibi basit nesneler) ve bazıları aralarına atandıklarında işaretçilerdir (örn. Dict1 = dict2). Onları nasıl tanıyabilirim? sadece bu deneyi onlarla deneyin. Değişken gezgini panelli IDE'lerde genellikle işaretçi mekanizması nesnelerinin tanımındaki bellek adresi ("@axbbbbbb ...") olarak görünür.

Konuyu araştırmanızı öneririm. Elbette bu konu hakkında daha çok şey bilen pek çok insan var. ("ctypes" modülüne bakın). Umarım yardımcı olur. Nesnelerin iyi kullanımının tadını çıkarın! Saygılarımızla, José Crespo


Öyleyse, bir değişkeni bir işleve göre iletmek için bir sözlük kullanmalıyım ve bir değişkeni bir int veya bir dize kullanarak başvuruya göre geçiremiyorum?
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

Gördüğünüz gibi ave bsadece iki farklı isimler bu referans olan aynı değişmez nesne (int) 1. Yazdığınız sonra ise a = 2, adı yeniden atama aa farklı nesne (int) 2, fakat bhiç başvuran devam 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)

Bunun yerine bir liste gibi değiştirilebilir bir nesneniz olsaydı ne olurdu [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)

Yine, aynı nesneye (listeye) [1]iki farklı adla ave b. Ancak şimdi biz aynı nesne kalırken bu listeyi mutasyona ve olabilir a, bhem kendisine başvuran devam edecek

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

1
Kimlik işlevini tanıttığınız için teşekkürler. Bu, şüphelerimin çoğunu çözdü.
2019

12

Bir bakış açısından, Python'da her şey bir göstericidir. Örneğiniz C ++ koduna çok benzer.

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

(Daha yakın bir eşdeğer, shared_ptr<Object>yerine bir tür kullanır int*.)

İşte bir örnek: form.data ['field'] ve form.field.value'un her zaman aynı değere sahip olmasını istiyorum. Tamamen gerekli değil ama güzel olacağını düşünüyorum.

Sen aşırı yapabilirsiniz __getitem__yılında form.databireyin sınıfında.


form.databir sınıf değil. Bunu yapmak gerekli mi, yoksa anında geçersiz kılabilir miyim? (Bu sadece bir python diktesi) Ayrıca, verilerin formalanlara erişmek için geri bir referansa sahip olması gerekir ... bu da uygulamayı çirkin hale getirir.
mpen

1

Bu bir python işaretçisidir (c / c ++ 'dan farklıdır)

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

Python'da bir işaretçiyi taklit etmenin bir yolu olarak aşağıdaki basit sınıfı etkili bir şekilde yazdım:

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)

İşte bir kullanım örneği (bir jupyter not defteri sayfasından):

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]

Tabii ki, bunun dikte öğeleri veya bir nesnenin nitelikleri için çalışmasını sağlamak da kolaydır. OP'nin istediğini globalleri () kullanarak yapmanın bir yolu bile var:

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)

Bu, 2 ve ardından 3 yazdıracaktır.

Küresel isim alanıyla bu şekilde uğraşmak, şeffaf bir şekilde korkunç bir fikir, ancak OP'nin istediğini yapmanın (eğer tavsiye edilmezse) mümkün olduğunu gösteriyor.

Örnek elbette oldukça anlamsız. Ancak bu sınıfı, geliştirdiğim uygulamada yararlı buldum: davranışı kullanıcı tarafından ayarlanabilen çok sayıda matematiksel parametre tarafından yönetilen matematiksel bir model, çeşitli türlerde (komut satırı argümanlarına bağlı oldukları için bilinmemektedir. derleme zamanında). Ve bir şeye erişim bir Parametre nesnesi içine alındıktan sonra, bu tür tüm nesneler tek tip bir şekilde manipüle edilebilir.

C veya C ++ işaretçisine çok benzemese de, bu, C ++ ile yazıyor olsaydım işaretçilerle çözeceğim bir sorunu çözüyor.


0

Aşağıdaki kod, C'deki işaretçilerin davranışını tam olarak taklit eder:

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    

İşte kullanım örnekleri:

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)

Ancak şimdi kişisel kütüphanemde bulduğum işaretçileri silme seçeneği de dahil olmak üzere daha profesyonel bir kod verme zamanı:

# 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

Bence değerleri tuhaf bir şekilde kutuladınız.
mpen

Yani: "zekice bir yol" mu yoksa "zekice olmayan bir yol" mu?
MikeTeX

Uhhh ... Rastgele bir sayıyla indekslenmiş global bir depolama için geçerli bir kullanım durumu görmekte zorlanıyorum.
mpen

Kullanım örneği: Ben bir algoritma mühendisiyim ve programcılarla çalışmam gerekiyor. Python ile çalışıyorum ve C ++ ile çalışıyorlar. Bazen onlar için bir algoritma yazmamı istiyorlar ve ben de kolaylık sağlamak için bunu C ++ 'ya olabildiğince yakın yazıyorum. İşaretçiler, örneğin ikili ağaçlar vb. İçin kullanışlıdır.
MikeTeX

Not: Global depolama alanı rahatsız ederse, onu sınıfın kendi düzeyinde global bir değişken olarak dahil edebilirsiniz, bu muhtemelen daha zariftir.
MikeTeX
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.