Указатели в Python? - PullRequest
       2

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

104 голосов
/ 24 июня 2010

Я знаю, что в 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

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

Ответы [ 7 ]

45 голосов
/ 24 июня 2010

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

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

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

43 голосов
/ 24 июня 2010

Я хочу, чтобы form.data['field'] и form.field.value всегда имели одно и то же значение

Это выполнимо, потому что оно включает в себя оформленные имена и индексацию - т. Е. полностью различных конструкций из голых имен 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, к ощущению «указателя», как вам кажется, - но важно понимать, что такие тонкости могут когда-либо работатьс индексированием и / или оформленными именами , никогда с голыми именами, как вы изначально просили!

33 голосов
/ 24 июня 2010

Это не ошибка, это особенность: -)

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

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

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

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

24 голосов
/ 18 сентября 2014

Да!В 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").Я надеюсь, что это полезно.Наслаждайтесь хорошим использованием объектов!С уважением, Хосе Креспо

11 голосов
/ 24 июня 2010

С одной точки зрения, все является указателем в 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 классе.

7 голосов
/ 14 февраля 2018
id(1)
1923344848  # identity of the location in my memory    
>> a = 1
>> b = a  # or equivalently b = 1, because 1 is immutable
>> id(a)
1923344848
>> id(b)
1923344848

Как видите, a и b - это просто имена, которые ссылаются на один и тот же объект 1.Если позже вы напишите a = 2, вы переназначите имя a другому объекту 2, но не b, который будет продолжать ссылаться на 1:

>> id(2)
1923344880
>> a = 2
>> id(a)
1923344880  # same as id(2)
>> id(b)
1923344848  # same as id(1)

Что произойдетесли у вас был изменчивый объект?

>> id([1])
328817608
>> id([1])
328664968  # different
>> a = [1]
>> id(a)
328817800
>> id(a)
328817800  # same as before
>> b = a  # not equivalent to b = [1]
>> id(b)
328817800  # same as id(a)

Теперь вы ссылаетесь на один и тот же объект списка по именам a и b.Вы можете изменить этот список, но он останется тем же объектом, и a и b будут продолжать ссылаться на него

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # same as before
>> id(b)
328817800  # same as before
0 голосов
/ 29 ноября 2018

Я написал следующий простой класс для эффективной эмуляции указателя в 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]

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

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

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

Хотя это не очень похоже на указатель C или C ++, это решает проблему, которую я бы решил с помощью указателей, если бы писал на C ++.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...