Покажчики в Python?


124

Я знаю , що Python не має покажчиків, але є спосіб , щоб цей вихід 2замість

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

?


Ось приклад: я хочу form.data['field']і form.field.valueзавжди мати однакове значення. Це не зовсім потрібно, але я думаю, це було б непогано.


Наприклад, у PHP я можу це зробити:

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

Вихід:

GeorgeGeorgeRalphRalph

ідеоне


Або як це в C ++ (я думаю, це правильно, але мій C ++ іржавий):

#include <iostream>
using namespace std;

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

    return 0;
}

28
Можливо, я можу задати питання, подібне до С.Лотта (але більш продуктивне): чи можете ви показати нам якийсь реальний код, де ви хотіли це зробити? Можливо, навіть іншою мовою, яка вам більше подобається? Цілком ймовірно, що проблема, яку ви намагаєтеся вирішити, піддасться більш пітонічному вирішенню, а зосередження уваги на "Я хочу покажчиків" затьмарює справжню відповідь.
Нед Батчелдер

8
Це не потребує великої фантазії; Я можу придумати десятки причин, щоб хотіти це зробити. Це просто не так, як це робиться на мовах, що не мають вказівників, як Python; вам потрібно загорнути його в контейнер, який не є інваріантним, як у відповіді Метта.
Гленн Мейнард

14
Ви б не писали функцію свопу в Python. Ви б написали a, b = b, a.
dan04

3
-1: Конструкція у питанні (а) безглузда, і (б), здається, ніхто не може надати приклад, який робить це розумним. Сказати, що є "десятки причин" - це не те саме, що розміщувати приклад.
S.Lott

1
@Mark: Крім того, я не "не згоден". Я збентежений. Я задаю питання, шукаю спосіб зрозуміти, що це таке, і чому ви вважаєте, що це так важливо.
S.Lott

Відповіді:


48

Я хочу form.data['field']і form.field.valueзавжди мати однакове значення

Це можливо, тому що вона включає в себе декоровані імена та індексації - тобто абсолютно різні конструкції з barenames a і bщо ви питаєте про, і з вашим запитом абсолютно неможливо. Навіщо просити щось неможливе і зовсім відмінне від (можливої) речі, якої ви насправді хочете ?!

Можливо, ви не усвідомлюєте, наскільки різко різні барені та прикрашені імена. Коли ви посилаєтесь на ім'я баре a, ви отримуєте саме той предмет, aякий останній час був пов'язаний у цій області (або виняток, якщо він не був пов'язаний у цій області) - це такий глибокий і фундаментальний аспект Python, що він може Можливо, підрив. Коли ви посилаєтесь на оформлене ім'я x.y, ви просите об'єкт (на який xпосилається об’єкт ), будь ласка, надайте " yатрибут" - і у відповідь на цей запит об'єкт може виконати абсолютно довільні обчислення (а індексація досить схожа: це також дозволяє проводити у відповідь довільні обчислення).

Тепер ваш приклад "фактичного десидерата" загадковий, оскільки в кожному випадку задіяні два рівні індексації або отримання атрибутів, тому тонкість, якої ви прагнете, може бути введена багатьма способами. Які ще атрибути, form.fieldмабуть, мають, наприклад, крім value? Без цього подальших .valueобчислень можливості включатимуть:

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

і

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

Наявність .valueпропозицій підбирати першу форму, а також своєрідну марну обгортку:

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

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

Якщо завдання , такі form.field.value = 23як також передбачається встановити запис в form.data, то оболонка повинна стати більш складним на насправді, і не всі , що марно:

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)

Останній приклад є приблизно таким же близьким, як наближається до Python, до сенсу "вказівника", як вам здається, - але важливо розуміти, що такі тонкощі коли-небудь можуть працювати лише з індексацією та / або оформленими іменами , ніколи з barename, як ви спочатку просили!


23
Я запитав це так, як це зробив, тому що я сподівався отримати загальне рішення, яке б спрацювало все, аж до "баременів", і я не мав на увазі конкретного випадку на той час - просто що я натрапив на це Проблема з попередньою ітерацією цього проекту, і я не хотів знову працювати. У всякому разі, це чудова відповідь! Однак недовіра менш цінується.
mpen

2
@ Марк, подивіться на коментарі до Вашого запитання, і Ви побачите, що "недовірливість" - це широко поширена реакція - і голосовий А говорить вам, що "не робіть цього, перейміть це", а далі - іде "це просто так". Хоча ви не можете «оцінити» Python-знаючих людей , що реагують з подивом до ваших оригінальним специфікаціям, вони є вельми дивні самі по собі ;-).
Алекс Мартеллі

30
Так, але ви, мабуть, здивовані моєю відсутністю пітонських знань ... ніби ми чужий вид: P
mpen

51

Ви не можете зробити це, змінивши лише цей рядок. Ви можете зробити:

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

Це створює список, призначає посилання на a, потім b також використовує посилання для встановлення першого елемента на 2, потім звертається за допомогою змінної b.


17
Саме таку непослідовність я ненавиджу щодо Python і цих динамічних мов. (Так, так, це насправді не "непослідовно", тому що ви змінюєте атрибут, а не посилання, але мені це все ще не подобається)
mpen

10
@ Марк: справді. Я знаю незліченну кількість (ну, декілька) людей, які витратили, можливо, години на пошуки "помилки" у своєму коді, а потім виявили, що це спричинено тим, що список не копіюється.
houbysoft

14
Тут немає непослідовності. І це не має нічого спільного з великою статичною протидинамічною дискусією. Якби вони були двома посиланнями на один і той же Java ArrayList, це був би той самий синтаксис модуля. Якщо ви використовуєте незмінні об'єкти (наприклад, кортежі), вам не доведеться турбуватися про зміну об'єкта через іншу посилання.
Меттью Флашен

Я використовував це кілька разів, найчастіше, щоб подолати відсутність "нелокальних" 2.x. Це не найкрасивіша річ, але вона прекрасно працює.
Гленн Мейнард

1
Це зовсім не суперечить тому, що об'єкт, якому ви призначаєте, aі bце список, а не значення у списку. Змінні не змінюють призначення, об'єкт - це той самий об’єкт. Якби воно змінилося, як у випадку зміни цілого числа (кожне з яких є різними об'єктами), aтепер було б присвоєно іншому об'єкту, і нічого не спонукає bнаслідувати його. Тут aне перепризначено, а значення всередині об'єкта, якому він призначений, змінюється. Оскільки bвін все ще пов'язаний з цим об'єктом, він відображатиме цей об'єкт та зміни значень всередині нього.
arkigos

34

Це не помилка, це особливість :-)

Коли ви дивитесь на оператора '=' в Python, не думайте з точки зору призначення. Ви не присвоюєте речі, а прив'язуєте їх. = - оператор прив'язки.

Отже, у своєму коді ви даєте значення 1 ім'я: a. Тоді ви даєте значення в імені 'a': b. Тоді ви прив'язуєте значення 2 до імені 'a'. Значення, пов'язане з b, не змінюється в цій операції.

Виходячи з мов, подібних С, це може бути заплутаним, але, коли ви звикнете до нього, ви виявите, що це допомагає вам чіткіше читати та міркувати про свій код: значення, яке має назву 'b', не зміниться, якщо ви явно змінити його. І якщо ви "імпортуєте це", ви побачите, що дзен Python заявляє, що Explicit краще, ніж неявний.

Зауважте також, що функціональні мови, такі як Haskell, також використовують цю парадигму, що має велике значення з точки зору надійності.


39
Знаєте, я відповів подібні десятки разів, і ніколи не розумів. В a = 1; b = a; a = 2;Python, C та Java поведінка точно однакова: b дорівнює 1. Чому цей фокус на "= не присвоєння, це обов'язкове"?
Нед Батчелдер

4
Ви присвоюєте речі. Ось чому його називають заявою про призначення . Відмінність, яку ви заявляєте, не має сенсу. І це не має нічого спільного з компільованою v. Інтерпретованою або статичною проти. Java - це мова компіляції зі статичною перевіркою типу, і вона також не має вказівників.
Метью Флашен

3
Що з C ++? "b" може бути посиланням на "a". Розуміння різниці між присвоєнням та прив’язкою має вирішальне значення для повного розуміння того, чому Марк не може робити те, що він хотів би зробити, і як створені такі мови, як Python. Концептуально (необов'язково в реалізації) "a = 1" не замінює блок пам'яті з назвою "a" з 1; він присвоює ім'я "a" вже існуючому об'єкту "1", що принципово відрізняється від того, що відбувається в C. Ось чому вказівники як концепція не можуть існувати в Python - вони стануть черствими наступного разу, коли оригінальна змінна була "призначена за".
Гленн Мейнард

1
@dw: Мені подобається такий спосіб мислення про це! «Обв’язування» - це гарне слово. @Ned: Вихід однаковий, так, але в C значення "1" копіюється в обидва, aі bтоді як у Python вони обоє посилаються на той самий "1" (я думаю). Таким чином, якби ви могли змінити значення 1 (як і для об'єктів), воно було б інакше. Що призводить до якихось дивних проблем з боксом / розпакуванням, я чую.
mpen

4
Різниця між Python та C - не те, що означає "призначення". Це те, що означає "змінна".
дан04,

28

Так! є спосіб використовувати змінну як вказівник у python!

Я шкодую, що багато відповідей були частково помилковими. В принципі, кожне рівнозначне (=) призначення має спільну адресу пам'яті (перевірити функцію id (obj)), але на практиці це не таке. Є змінні, однакова ("=") поведінка працює в останньому терміні як копія простору пам'яті, переважно в простих об'єктах (наприклад, "int" об'єкт) та інших, в яких немає (наприклад, "список", "dict" об'єкти) .

Ось приклад призначення покажчика

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

Ось приклад призначення копії

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

Призначення покажчика - це досить корисний інструмент для згладжування без втрати зайвої пам'яті в певних ситуаціях для виконання зручного коду,

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

але потрібно знати про це, щоб запобігти помилкам у коді.

На закінчення, деякі змінні за замовчуванням є іменами (прості об'єкти, такі як int, float, str, ...), а деякі - покажчики, коли вони призначаються між ними (наприклад, dict1 = dict2). Як їх розпізнати? просто спробуйте цей експеримент з ними. У IDE із змінною панеллю провідника зазвичай виявляється адреса пам'яті ("@axbbbbbbb ...") у визначенні об'єктів механізму покажчика.

Пропоную дослідити в темі. Є багато людей, які точно знають набагато більше цієї теми. (див. модуль "ctypes"). Я сподіваюся, що це корисно. Насолоджуйтесь вдалим використанням предметів! З повагою, Жозе Креспо


Отже, я повинен використовувати словник для передачі змінної за посиланням на функцію, і я не можу передавати змінну за посиланням за допомогою int або string?
Сем

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

Як ви бачите, aі bце лише дві різні назви, які посилаються на той самий незмінний об'єкт (int) 1. Якщо пізніше ви пишете a = 2, ви перепризначити ім'я aдо іншого об'єкту (INT) 2, але по- b, як і раніше посилається на 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)

Що буде, якби натомість у вас був об'єкт, що змінюється, наприклад список [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)

Знову ми посилаємось на один і той же об’єкт (список) [1]двома різними іменами aта b. Однак тепер ми можемо мутувати цей список , поки він залишається тим же об'єктом, і a, bобидва по- , як і раніше посилається на нього

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

1
Дякуємо за введення функції id Це вирішує багато моїх сумнівів.
haudoing

12

З однієї точки зору, все є вказівником на Python. Ваш приклад дуже добре працює як код C ++.

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

(Більш близький еквівалент використовуватиме якийсь тип shared_ptr<Object>замість int*.)

Ось приклад: я хочу, щоб form.data ['field'] і form.field.value завжди мали однакове значення. Це не зовсім потрібно, але я думаю, це було б непогано.

Це можна зробити, перевантажившись __getitem__у form.dataкласі.


form.dataне клас. Чи потрібно його зробити одним, або я можу його змінити на льоту? (Це просто диктон пітона) Також, дані повинні мати посилання назад formдля доступу до полів ... що робить реалізацію цього некрасивим.
mpen

1

Це вказівник пітона (різний 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

Я написав такий простий клас як, фактично, спосіб емуляції вказівника в 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)

Ось приклад використання (зі сторінки зошита з юпітером):

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]

Звичайно, це також легко зробити цю роботу для dict елементів або атрибутів об'єкта. Існує навіть спосіб зробити те, про що вимагала ОП, використовуючи глобали ():

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)

Це буде надруковано 2, а потім 3.

Таким чином, возитися із глобальним простором імен є явно жахливою ідеєю, але це показує, що можна (якщо не доцільно) робити те, про що вимагає ОП.

Приклад, звичайно, досить безглуздий. Але я знайшов цей клас корисним у додатку, для якого я його розробив: математична модель, поведінка якої керується численними математичними параметрами, встановленими користувачем, різних типів (які, оскільки вони залежать від аргументів командного рядка, не відомі під час компіляції). І як тільки доступ до чогось інкапсульований в об'єкт Параметра, всіма цими об'єктами можна маніпулювати рівномірним чином.

Хоча це не дуже схоже на покажчик C або C ++, це вирішує проблему, яку я вирішив би за допомогою покажчиків, якби писав на C ++.


0

Наступний код точно імітує поведінку покажчиків на 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    

Ось приклади використання:

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)

Але зараз час надати більш професійний код, включаючи можливість видалення покажчиків, який я щойно знайшов у своїй особистій бібліотеці:

# 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

Я думаю, що ти щойно боксував цінності.
mpen

Ви маєте на увазі: «розумний шлях» чи «не розумний шлях»?
MikeTeX

Угу ... я намагаюся бачити дійсний випадок використання для глобального сховища, індексованого випадковим числом.
mpen

Приклад використання: Я інженер-алгоритм і мені потрібно працювати з програмістами. Я працюю з Python, а вони працюють із C ++. Іноді вони просять мене написати алгоритм для них, і я пишу його якомога ближче до С ++ для їх зручності. Покажчики корисні, наприклад, для двійкових дерев тощо
MikeTeX

Примітка: якщо глобальний об'єм зберігання даних, ви можете включити його як глобальну змінну на рівні самого класу, який, напевно, є більш елегантним.
MikeTeX
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.