Указатели в Python?

124

Я знаю, что у Python нет указателей, но есть ли способ 2вместо этого получить этот yield

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

ideone


Или так в 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;
}
mpen
источник
28
Возможно, я смогу задать вопрос, похожий на вопрос С.Лотта (но более продуктивный): не могли бы вы показать нам реальный код, в котором вы хотели бы это сделать? Возможно, даже на другом языке, который вам больше по вкусу? Вполне вероятно, что проблема, которую вы пытаетесь решить, поддастся более питоническому решению, и сосредоточение внимания на «мне нужны указатели» затрудняет понимание реального ответа.
Нед Батчелдер,
8
Это не требует большого воображения; Я могу придумать десятки причин, по которым я хочу это сделать. Это просто не так, как это делается в языках без указателей, таких как Python; вам нужно обернуть его в контейнер, который не является неизменным, как в ответе Мэтта.
Glenn Maynard
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 примерно настолько близок к смыслу «указателя», насколько вам кажется, - но важно понимать, что такие тонкости могут работать только с индексированием и / или декорированными именами , никогда с голыми именами, как вы изначально просили!

Алекс Мартелли
источник
23
Я спросил, как и сделал, потому что я надеялся получить общее решение, которое будет работать для всего, вплоть до "голых имен", и в то время у меня не было в голове конкретного случая - просто я столкнулся с этим проблема с предыдущей итерацией этого проекта, и я не хотел сталкиваться с ней снова. В любом случае, это отличный ответ! Однако недоверие ценится меньше.
mpen
2
@Mark, просмотрите комментарии к вашему Q, и вы увидите, что "недоверие" - это широко распространенная реакция - и получивший наибольшее количество голосов A говорит вам "не делай этого, перебери это", а затем следует тот, который идет "просто как есть". Хотя вы не можете «оценить» Python-знающих людей , реагирующих с удивлением к вашим оригинальным спецификациям, они являются весьма удивительны сами по себе ;-).
Alex Martelli
30
Да, но вы, кажется, удивлены моим отсутствием знаний о Python ... как будто мы чужой вид: P
mpen
51

Вы не можете сделать это, изменив только эту строку. Ты можешь сделать:

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

Это создает список, присваивает ссылку a, затем также b, использует ссылку a для установки первого элемента в 2, а затем осуществляет доступ с помощью ссылочной переменной b.

Мэтью Флашен
источник
17
Именно такую ​​несогласованность я ненавижу в Python и этих динамических языках. (Да, да, это не совсем «непоследовательно», потому что вы меняете атрибут, а не ссылку, но мне это все равно не нравится)
mpen
10
@Mark: действительно. Я знаю бесчисленное количество (ну, несколько) людей, которые, возможно, часами искали «ошибку» в своем коде, а затем обнаружили, что она вызвана тем, что список не был скопирован жестко.
Houbysoft
14
Нет противоречий. И это не имеет ничего общего с великой дискуссией о статике и динамике. Если бы это были две ссылки на один и тот же Java ArrayList, это был бы один и тот же синтаксис по модулю. Если вы используете неизменяемые объекты (например, кортежи), вам не нужно беспокоиться об изменении объекта с помощью другой ссылки.
Мэтью Флашен,
Я использовал это несколько раз, чаще всего, чтобы обойти недостаток «нелокальности» в 2.x. Это не самое приятное занятие, но в крайнем случае работает отлично.
Glenn Maynard
1
Это не противоречит вообще , так как объект , который вы присваиваете aи bсписок, а не значение в списке. Переменные не меняют назначения, объект - это тот же объект. Если бы он изменился, как в случае изменения целого числа (каждый из которых является разными объектами), aтеперь он будет назначен другому объекту, и нет ничего, что побуждает bпоследовать его примеру. Здесь aне переназначается, а изменяется значение внутри объекта, которому он назначен. Поскольку bвсе еще привязан к этому объекту, он будет отражать этот объект и изменения значений в нем.
arkigos
34

Это не баг, а фича :-)

Когда вы смотрите на оператор '=' в Python, не думайте о присваивании. Вы не назначаете вещи, вы их связываете. = - это оператор привязки.

Итак, в вашем коде вы даете значение 1 имя: a. Затем вы даете значение в 'a' имя: b. Затем вы привязываете значение 2 к имени 'a'. Значение, связанное с b, не изменяется в этой операции.

Исходя из C-подобных языков, это может сбивать с толку, но как только вы привыкнете к нему, вы обнаружите, что это помогает вам читать и рассуждать о своем коде более ясно: значение с именем 'b' не изменится, если вы явно измените его. И если вы сделаете «импортировать это», вы обнаружите, что дзен Python утверждает, что явное лучше, чем неявное.

Также обратите внимание, что функциональные языки, такие как Haskell, также используют эту парадигму, что имеет большое значение с точки зрения надежности.

Дэвид Харкс
источник
39
Знаете, я читал подобные ответы десятки раз и никогда не понимал этого. Поведение a = 1; b = a; a = 2;точно такое же в Python, C и Java: b равно 1. Почему этот акцент делается на «= не присвоение, а привязка»?
Нед Батчелдер,
4
Вы назначаете вещи. Вот почему это называется оператором присваивания . Проводимое вами различие не имеет смысла. И это не имеет ничего общего с скомпилированным v. Интерпретируемым или статическим v. Динамическим. Java - это скомпилированный язык со статической проверкой типов, и он также не имеет указателей.
Мэтью Флашен,
3
А как насчет C ++? «b» может быть ссылкой на «a». Понимание разницы между присваиванием и привязкой имеет решающее значение для полного понимания того, почему Марк не может делать то, что он хотел бы делать, и как создаются языки, такие как Python. Концептуально (не обязательно в реализации) «a = 1» не перезаписывает блок памяти с именем «a» на 1; он присваивает имя «a» уже существующему объекту «1», которое фундаментально отличается от того, что происходит в C. Вот почему указатели как концепция не могут существовать в Python - они станут устаревшими в следующий раз, когда исходная переменная была «присвоена».
Glenn Maynard
1
@dw: Мне нравится такой образ мышления! «Связывание» - хорошее слово. @Ned: Выходной сигнал такой же, да, но в C значение «1» копируется как aи в bто время как в Python, оба они относятся к той же «1» (я думаю). Таким образом, если бы вы могли изменить значение 1 (как с объектами), оно было бы другим. Я слышал, что приводит к некоторым странным проблемам с боксом / распаковкой.
mpen
4
Разница между Python и C не в том, что означает «назначение». Это то, что означает «переменная».
dan04
28

Да! есть способ использовать переменную в качестве указателя в python!

С сожалением вынужден сказать, что многие ответы были частично неправильными. В принципе, каждое равное (=) присвоение разделяет адрес памяти (проверьте функцию id (obj)), но на практике это не так. Есть переменные, поведение которых равно ("=") работает в последнем термине как копия пространства памяти, в основном в простых объектах (например, объект "int"), и другие, в которых нет (например, объекты "list", "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 с переменной панелью обозревателя обычно появляется адрес памяти («@axbbbbbb ...») в определении объектов механизма указателя.

Предлагаю разобраться в теме. Есть много людей, которые наверняка знают гораздо больше по этой теме. (см. модуль "ctypes"). Надеюсь, это поможет. Наслаждайтесь хорошим использованием предметов! С уважением, Хосе Креспо

Хосе Креспо Барриос
источник
Итак, мне нужно использовать словарь для передачи переменной по ссылке на функцию, и я не могу передать переменную по ссылке, используя int или строку?
Сэм
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классе «S.

dan04
источник
form.dataне класс. Нужно ли делать это единым целым, или я могу отменить это на лету? (Это просто Python dict) Кроме того, данные должны иметь обратную ссылку formдля доступа к полям ... что делает реализацию этой уродливой.
mpen
1

Это указатель на Python (отличается от 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)

Вот пример использования (со страницы записной книжки 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]

Конечно, это также легко сделать для элементов dict или атрибутов объекта. Есть даже способ сделать то, что просил OP, с помощью 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)

Будет напечатано 2, а затем 3.

Такой способ возиться с глобальным пространством имен - это явно ужасная идея, но она показывает, что можно (если нецелесообразно) делать то, что просил OP.

Этот пример, конечно, довольно бессмысленный. Но я обнаружил, что этот класс полезен в приложении, для которого я его разработал: математическая модель, поведение которой регулируется множеством задаваемых пользователем математических параметров различных типов (которые, поскольку они зависят от аргументов командной строки, неизвестны. во время компиляции). И как только доступ к чему-либо инкапсулирован в объект Parameter, всеми такими объектами можно управлять единообразно.

Хотя это не очень похоже на указатель 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
MikeTeX
источник
Я думаю, вы только что странным образом упаковали значения.
mpen
Вы имеете в виду: «умный способ» или «неумный способ»?
MikeTeX
Уххх ... Я изо всех сил пытаюсь найти допустимый вариант использования глобального хранилища, индексированного случайным числом.
mpen
Пример использования: я разработчик алгоритмов и мне приходится работать с программистами. Я работаю с Python, а они работают с C ++. Иногда они просят меня написать для них алгоритм, и я пишу его как можно ближе к C ++ для их удобства. Указатели полезны, например, для двоичных деревьев и т. Д.
MikeTeX
Примечание: если глобальное хранилище беспокоит, вы можете включить его как глобальную переменную на уровне самого класса, что, вероятно, более элегантно.
MikeTeX