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

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

28
おそらく私はS.Lottのような質問をすることができます(より生産的です):これを実行したい場所に実際のコードを見せていただけますか?おそらくあなたの好みに合った別の言語でさえ?あなたが解決しようとしている問題は、よりPython的な解決策に向いている可能性が高く、「ポインタが欲しい」に焦点を合わせると、本当の答えがわかりにくくなります。
Ned Batchelder、2010年

8
想像力はあまり必要ありません。これをやりたいと思う理由はたくさんあります。それは、Pythonのようなポインターのない言語で行われる方法ではありません。マットの答えのように、それを不変ではないコンテナでラップする必要があります。
Glenn Maynard

14
Pythonでスワップ関数を作成することはありません。あなたが書くでしょうa, b = b, a
dan04

3
-1:問題の構成要素は(a)意味がなく、(b)それを賢明にする例を提供できる人はいないようです。「多数の理由」があると言うことは、例を投稿することと同じではありません。
S.Lott

1
@マーク:また、私は「不満」ではありません。よくわかりません。私は質問をして、これが何であるか、なぜそれがそれほど重要であると考えるのかを理解する方法を探しています。
S.Lott、

回答:


48

私はしたいform.data['field']form.field.value常に同じ値を持つように

これは、装飾された名前とインデックス付けが含まれるため、実現可能です。つまり、ベアネームとは完全に異なる構成であり、あなたが求めているものであり、要求に応じることはまったく不可能です。なぜ、あなたが実際に望んでいる(可能性のある)ものとはまったく異なるまったく異なるものを求めるのですか? ab

たぶんあなたは、ベアネームと装飾された名前がどれほど劇的に異なるかを理解していないでしょう。barenameを参照するとa、オブジェクトaがこのスコープで最後にバインドされたのとまったく同じになります(または、このスコープにバインドされていない場合は例外です)-これは、Pythonの非常に深く根本的な側面であり、おそらく破壊されません。装飾された名前を参照するときはx.y、オブジェクト(オブジェクトがx参照するオブジェクト)に「y属性」を提供するように要求しています。その要求に応答して、オブジェクトは完全に任意の計算を実行できます(インデックス作成は非常に似ています。また、応答として任意の計算を実行できます)。

ここで、「実際のデシデラタ」の例は不思議です。どちらの場合も、2つのレベルのインデックス付けまたは属性取得が含まれているため、切望する繊細さがさまざまな方法で導入される可能性があります。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
@マーク:確かに。コード内の「バグ」を検索するために何時間も費やし、それがハードコピーされていないリストが原因であることが判明した無数の(まあ、数人の)人々を知っています。
houbysoft

14
矛盾はありません。そして、それは素晴らしい静的対動的な議論とは何の関係もありません。それらが同じJava ArrayListへの2つの参照である場合、同じモジュロ構文になります。不変のオブジェクト(タプルなど)を使用する場合、オブジェクトが別の参照によって変更されることを心配する必要はありません。
Matthew Flaschen

私はこれを数回使用しましたが、最も一般的には2.xの「非ローカル」の欠如を回避するためです。これは最もきれいなことではありませんが、ピンチでうまく機能します。
Glenn Maynard

1
オブジェクトがあなたに割り当てているので、これは全く矛盾しないab、リストではなく、リスト内の値です。変数は割り当てを変更していません。オブジェクトは同じオブジェクトです。整数を変更する場合(それぞれ異なるオブジェクト)のように変更された場合は、a別のオブジェクトに割り当てられb、追随を促すメッセージは何もありません。ここでaは、は再割り当てされておらず、割り当て先のオブジェクト内の値が変化しています。以来b、まだそのオブジェクトにバインドされ、そのオブジェクトとその中の値に変更が反映されます。
arkigos 2017年

34

これはバグではなく、機能です:-)

Pythonで「=」演算子を見るときは、割り当てについて考えないでください。物事を割り当てるのではなく、それらをバインドします。=はバインディング演算子です。

したがって、コードでは、値1に名前を付けています。次に、「a」の値に名前を付けます。次に、値2を名前「a」にバインドします。この操作では、bにバインドされた値は変更されません。

Cのような言語から来ると、これは混乱する可能性がありますが、一度慣れると、コードをより明確に読み、推論するのに役立つことがわかります。「b」という名前の値は、明示的に変更します。そして、「これをインポート」すると、PythonのZenは、明示的が暗黙的よりも優れていると述べています。

また、Haskellなどの関数型言語もこのパラダイムを使用しており、堅牢性の点で大きな価値があります。


39
ご存知のように、私はこのような答えを何十回も読んだことがあり、理解したことがありません。の動作はa = 1; b = a; a = 2;Python、C、Javaでまったく同じです:bは1です。なぜ「=は代入ではなく、バインディングである」ということに焦点を当てているのですか?
Ned Batchelder 2010年

4
あなたは物事を割り当てます。これが、割り当てステートメントと呼ばれる理由です。あなたが言っている区別は意味をなさない。そして、これはコンパイルされたv。インタプリタまたは静的v。ダイナミックとは関係ありません。Javaは静的型チェックを備えたコンパイル言語であり、ポインタもありません。
Matthew Flaschen

3
C ++はどうですか?「b」は「a」への参照である場合があります。割り当てとバインディングの違いを理解することは、マークが自分のやりたいことを実行できない理由と、Pythonなどの言語がどのように設計されているかを完全に理解するために重要です。概念的には(必ずしも実装ではありません)、「a = 1」は「a」という名前のメモリブロックを1で上書きしません。既存のオブジェクト「1」に名前「a」を割り当てます。これは、Cで発生するものとは根本的に異なります。そのため、Pythonに概念としてのポインターは存在できません。元の変数は「割り当てられました」。
Glenn Maynard

1
@dw:私はこの考え方が好きです!「製本」はいい言葉です。@Ned:出力はい、同じであるが、Cに「1」の値は、両方にコピーされるabPythonで、一方、それらの両方が参照する、同じ「1」(と思います)。したがって、(オブジェクトの場合と同様に)1の値を変更できる場合、値は異なります。奇妙なボクシング/アンボクシングの問題につながると聞いています。
mpen

4
PythonとCの違いは、「代入」が意味するものではありません。それが「変数」の意味です。
dan04

28

はい!Pythonでポインタとして変数を使用する方法があります!

回答の多くが部分的に間違っていたと申し訳ありません。原則として、すべてのequal(=)割り当てはメモリアドレスを共有します(id(obj)関数を確認してください)が、実際にはそうではありません。equal( "=")動作がメモリスペースのコピーとして機能する変数は、主に単純なオブジェクト(たとえば、 "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ちょうど2人の異なる名前を参照している同じ不変オブジェクト(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]を2つの異なる名前aとで参照していますb。ただし、これで、同じオブジェクトのままでこのリストを変更できab両方とも引き続き参照します。

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

1
id関数を導入していただきありがとうございます。これは私の疑問の多くを解決します。
1

12

1つの観点から見ると、すべてが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クラスではありません。1つにする必要がありますか、それともその場でオーバーライドできますか?(それは単なる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

値を奇妙な方法でボックス化したと思います。
mpen

つまり、「賢い方法」または「賢くない方法」ですか?
MikeTeX

ええと...乱数でインデックス付けされたグローバルストレージの有効なユースケースを見つけるのに苦労しています。
mpen

使用例:私はアルゴリズムエンジニアであり、プログラマと協力する必要があります。私はPythonを使用しており、C ++を使用しています。時々、彼らは私に彼らのためにアルゴリズムを書くように頼みます、そして私は彼らの便宜のために可能な限りC ++の近くにそれを書きます。ポインターは、たとえば二分木などに役立ちます
MikeTeX '26

注:グローバルストレージが気になる場合は、クラス自体のレベルでグローバル変数として含めることができます。これはおそらくよりエレガントです。
MikeTeX
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.