Pythonのポインター:ポイントは何ですか?

Pythonのポインター:ポイントは何ですか?

CやC ++のような低レベル言語で作業したことがあるなら、おそらくポインターを聞いたことがあるでしょう。 ポインターを使用すると、コードの一部を非常に効率的に作成できます。 また、初心者にとって混乱を招き、専門家であってもさまざまなメモリ管理バグを引き起こす可能性があります。 Pythonのどこにありますか?また、Pythonでポインターをどのようにシミュレートできますか?

ポインターは、CおよびC ++で広く使用されています。 基本的に、これらは別の変数のメモリアドレスを保持する変数です。 ポインターの復習については、https://www.tutorialspoint.com/cprogramming/c_pointers.htm [Cポインターの概要]をご覧ください。

この記事では、Pythonのオブジェクトモデルをより深く理解し、Pythonのポインターが実際に存在しない理由を学びます。 ポインターの動作を模倣する必要がある場合は、メモリ管理の悪夢を伴わずにPythonでポインターをシミュレートする方法を学習します。

この記事では、次のことを行います。

  • Pythonのポインターが存在しない理由を学ぶ

  • C変数とPython名の違いを調べる

  • Pythonでポインターをシミュレートする

  • `+ ctypes +`を使用して実際のポインターを試す

*注意:*この記事では、「Python」はCでのPythonのリファレンス実装(「CPython」とも呼ばれる)を指します。 この記事では言語の内部について説明しているため、これらの注意事項はCPython 3.7には当てはまりますが、将来または過去の言語の繰り返しでは当てはまらない場合があります。

*無料ボーナス:*リンク:[Python Tricks:The Book]の章にアクセスするには、ここをクリックして、Pythonのベストプラクティスを簡単な例とともに示します。すぐに適用して、より美しい+ Pythonコードを記述できます。

Pythonにポインターがないのはなぜですか?

真実は私が知らないということです。 Pythonのポインターはネイティブに存在できますか? おそらく、しかし、ポインターはhttps://www.python.org/dev/peps/pep-0020/#id3[Pythonの禅]に反しているようです。 ポインターは、明示的ではなく暗黙的な変更を推奨します。 多くの場合、特に初心者にとっては、単純ではなく複雑です。 さらに悪いことに、彼らは自分自身を足で撃つ方法を懇願するか、またはあなたが想定されていなかった記憶の一部から読むような本当に危険なことをします。

Pythonは、メモリアドレスなどの実装の詳細をユーザーから抽象化しようとする傾向があります。 Pythonは、速度よりもユーザビリティに重点を置くことがよくあります。 その結果、Pythonのポインターは実際には意味がありません。 恐れることはありませんが、Pythonはデフォルトで、ポインターを使用する利点をいくつか提供します。

Pythonのポインターを理解するには、Pythonの実装の詳細を少し迂回する必要があります。 具体的には、次のことを理解する必要があります。

  1. 不変オブジェクトと可変オブジェクト

  2. Python変数/名前

メモリアドレスを保持して、始めましょう。

Pythonのオブジェクト

Pythonでは、すべてがオブジェクトです。 証明のために、REPLを開き、 `+ isinstance()+`を使用して探索できます。

>>>

>>> isinstance(1, object)
True
>>> isinstance(list(), object)
True
>>> isinstance(True, object)
True
>>> def foo():
...     pass
...
>>> isinstance(foo, object)
True

このコードは、Pythonのすべてが実際にオブジェクトであることを示しています。 各オブジェクトには、少なくとも3つのデータが含まれます。

  • 参照カウント

  • Type *値

reference countはメモリ管理用です。 Pythonのメモリ管理の内部の詳細については、https://realpython.com/python-memory-management/[Pythonのメモリ管理]を参照してください。

型はCPythonレイヤーで使用され、実行時の型の安全性を確保します。 最後に、値があります。これは、オブジェクトに関連付けられている実際の値です。

ただし、すべてのオブジェクトが同じというわけではありません。 理解する必要があるもう1つの重要な違いがあります。不変オブジェクトと可変オブジェクトです。 オブジェクトのタイプの違いを理解することは、Pythonのポインターであるタマネギの最初の層を明確にするのに本当に役立ちます。

不変オブジェクトと可変オブジェクト

Pythonには、2種類のオブジェクトがあります。

* 不変オブジェクト*は変更できません。
  1. *変更可能なオブジェクト*は変更できます。

この違いを理解することは、Pythonでポインターのランドスケープをナビゲートする最初の鍵です。 一般的なタイプの内訳と、それらが可変か不変かを以下に示します。

Type Immutable?

int

Yes

float

Yes

bool

Yes

complex

Yes

tuple

Yes

frozenset

Yes

str

Yes

list

No

set

No

dict

No

ご覧のとおり、一般的に使用される多くのプリミティブ型は不変です。 Pythonを書くことで、これを自分で証明できます。 Python標準ライブラリのツールがいくつか必要です。

  1. + id()+ は、オブジェクトのメモリアドレスを返します。

  2. + is + は、2つのオブジェクトのメモリアドレスが同じ場合に限り、 `+ True +`を返します。

ここでも、REPL環境でこれらを使用できます。

>>>

>>> x = 5
>>> id(x)
94529957049376

上記のコードでは、値「5」を「+ x +」に割り当てています。 追加してこの値を変更しようとすると、新しいオブジェクトが取得されます。

>>>

>>> x += 1
>>> x
6
>>> id(x)
94529957049408

上記のコードは `+ x +`の値を変更するように見えますが、応答として_new_オブジェクトを取得しています。

`+ str +`型も不変です:

>>>

>>> s = "real_python"
>>> id(s)
140637819584048
>>> s += "_rocks"
>>> s
'real_python_rocks'
>>> id(s)
140637819609424

繰り返しますが、 `+ s `は `+ = +`操作の後に_different_メモリアドレスで終わります。

ボーナス: `++ = +`演算子はさまざまなメソッド呼び出しに変換されます。

+ list +`のような一部のオブジェクトでは、 `++ = +`は `+ iadd ()+(in-place add)に変換されます。 これにより、 `+ self `が変更され、同じIDが返されます。 ただし、 ` str `と ` int `にはこれらのメソッドがなく、 ` iadd ()`の代わりに ` add ()+`が呼び出されます。

詳細については、Python https://docs.python.org/3/reference/datamodel.html#object.iadd [データモデルドキュメント]をご覧ください。

文字列 `+ s +`を直接変更しようとすると、エラーが発生します。

>>>

>>> s[0] = "R"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

上記のコードは失敗し、Pythonは `+ str `がこの突然変異をサポートしていないことを示しています。これは、 ` str +`型は不変であるという定義に沿っています。

`+ list +`のような可変オブジェクトとは対照的です:

>>>

>>> my_list = [1, 2, 3]
>>> id(my_list)
140637819575368
>>> my_list.append(4)
>>> my_list
[1, 2, 3, 4]
>>> id(my_list)
140637819575368

このコードは、2種類のオブジェクトの大きな違いを示しています。 `+ my_list `には元々idがあります。 ` 4 `がリストに追加された後でも、 ` my_list `には_same_ idがあります。 これは、 ` list +`タイプが可変であるためです。

リストが変更可能であることを示す別の方法は、割り当てを使用することです。

>>>

>>> my_list[0] = 0
>>> my_list
[0, 2, 3, 4]
>>> id(my_list)
140637819575368

このコードでは、 `+ my_list `を変更し、その最初の要素を ` 0 +`に設定します。 ただし、この割り当て後も同じIDを維持します。 可変および不変オブジェクトが邪魔にならないため、https://realpython.com/pycon-guide/#what-to-do-at-pycon [Python enlightenment]への旅の次のステップは、Pythonの変数エコシステムを理解することです。

変数を理解する

Python変数は、CまたはC ++の変数と根本的に異なります。 実際、Pythonには変数さえありません。 _Pythonには変数ではなく名前があります。

これはつまらなく見えるかもしれませんが、ほとんどの場合そうです。 ほとんどの場合、Pythonの名前を変数として考えることは完全に受け入れられますが、違いを理解することは重要です。 これは、Pythonでポインターのトリッキーな主題をナビゲートしている場合に特に当てはまります。

違いを理解しやすくするために、Cで変数がどのように機能するか、それらが何を表すかを見て、それをPythonでの名前の機能と比較することができます。

Cの変数

変数 `+ x +`を定義する次のコードがあるとしましょう:

int x = 2337;

この1行のコードには、実行時にいくつかの明確なステップがあります。

  1. 整数に十分なメモリを割り当てます

  2. そのメモリの場所に値「2337」を割り当てます

  3. `+ x +`がその値を指していることを示す

メモリを簡略化して表示すると、次のようになります。

Xのメモリ内表現(2337)、width = 311、高さ= 171

ここでは、変数「+ x 」の偽のメモリ位置が「 0x7f1 」で、値が「+2337」であることがわかります。 プログラムの後半で、「+ x +」の値を変更する場合、次の操作を実行できます。

x = 2338;

上記のコードは、新しい値( + 2338 +)を変数 `+ x `に割り当て、それによって以前の値を上書きします。 これは、変数 ` x +`が mutable であることを意味します。 更新されたメモリレイアウトには、新しい値が表示されます。

X(2338)の新しいインメモリ表現、幅= 311、高さ= 171

「+ x 」の場所は変更されず、値自体だけが変更されたことに注意してください。 これは重要なポイントです。 これは、 ` x +`が単に名前ではなく、メモリの場所であることを意味します。

この概念を考えるもう1つの方法は、所有権の観点です。 ある意味では、 `+ x `がメモリロケーションを所有します。 ` x +`は、最初は空のボックスであり、整数値を格納できる整数を1つだけ収めることができます。

値を「+ x 」に割り当てると、「 x 」が所有するボックスに値が配置されます。 新しい変数( ` y +`)を導入したい場合は、次のコード行を追加できます。

int y = x;

このコードは、「+ y 」という_new_ボックスを作成し、値を「 x +」からボックスにコピーします。 これで、メモリレイアウトは次のようになります。

X(2338)およびY( 2338)、width = 716、height = 171

「+ y 」の新しい場所「 0x7f5 」に注意してください。 「 x 」の値が「 y 」にコピーされたとしても、変数「 y 」はメモリ内の新しいアドレスを所有します。 したがって、 ` x `に影響を与えずに ` y +`の値を上書きできます。

y = 2339;

これで、メモリレイアウトは次のようになります。

Yの表現を更新(2339)、width = 716、 height = 171

繰り返しになりますが、 `+ y `の値は変更しましたが、その場所は_not_です。 さらに、元の ` x +`変数にはまったく影響していません。 これは、Pythonの名前がどのように機能するかとはまったく対照的です。

Pythonの名前

Pythonには変数がありません。 名前があります。 はい、これは重要な点であり、変数という用語は好きなだけ使用できます。 変数と名前には違いがあることを知っておくことが重要です。

上記のCの例から同等のコードを取得して、Pythonで記述しましょう。

>>>

>>> x = 2337

Cの場合と同様に、上記のコードは実行中にいくつかの異なるステップに分割されます。

  1. `+ PyObject +`を作成する

  2. `+ PyObject +`のタイプコードを整数に設定します

  3. `+ PyObject `の値を ` 2337 +`に設定します

  4. 「+ x +」という名前を作成します

  5. 新しい「+ PyObject 」を指す「 x +」

  6. `+ PyObject +`の参照カウントを1増やします

注意: https://github.com/python/cpython/blob/v3.7.3/Include/object.h#L101 [+ PyObject +]はPythonの `+ object +`とは異なります。 CPythonに固有であり、すべてのPythonオブジェクトの基本構造を表します。

「+ PyObject 」はC構造体として定義されているため、「 typecode 」または「 refcount 」を直接呼び出せない理由がわからない場合は、構造体に直接アクセスできないためです。 https://docs.python.org/3/library/sys.html#sys.getrefcount [` sys.getrefcount()+`]のようなメソッド呼び出しは、内部の取得に役立ちます。

メモリ内では、次のようになります。

X(2337)のPythonインメモリ表現、幅= 716、height = 291

メモリレイアウトは、以前のCレイアウトとは大きく異なることがわかります。 値「2337」が存在するメモリブロックを所有する「+ x 」の代わりに、新しく作成されたPythonオブジェクトが「+2337」が存在するメモリを所有します。 Pythonの名前「+ x 」は、C変数「 x +」がメモリ内の静的スロットを所有していたように、_any_メモリアドレスを直接所有していません。

新しい値を `+ x +`に割り当てようとすると、次のことを試すことができます。

>>>

>>> x = 2338

ここで起こっていることは、Cの同等のものとは異なりますが、Pythonの元のバインドとあまり違いはありません。

このコード:

  • 新しい `+ PyObject +`を作成します

  • `+ PyObject +`のタイプコードを整数に設定します

  • `+ PyObject `の値を ` 2338 +`に設定します

  • 新しい「+ PyObject 」を指す「 x +」

  • 新しい `+ PyObject +`の参照カウントを1増やします

  • 古い `+ PyObject +`の参照カウントを1減らします

メモリー内では、次のようになります。

この図は、「+ x 」がオブジェクトへの参照を指し、以前のようにメモリ空間を所有していないことを説明するのに役立ちます。 また、 ` x = 2338 `コマンドは割り当てではなく、名前 ` x +`を参照にバインドすることも示しています。

さらに、( `+ 2337 +`値を保持していた)前のオブジェクトは、参照カウント0でメモリに保存され、https://docs.python.org/3/faq/designによってクリーンアップされます。 .html?highlight = garbage%20collect#how-does-python-manage-memory [garbage collector]。

Cの例のように、新しい名前 `+ y +`をミックスに導入できます。

>>>

>>> y = x

メモリには、新しい名前が付けられますが、必ずしも新しいオブジェクトではありません。

2338

これで、新しいPythonオブジェクトが作成されたのではなく、同じオブジェクトを指す新しい名前が作成されたことがわかります。 また、オブジェクトの参照カウントが1つ増えました。 オブジェクトの同一性をチェックして、それらが同じであることを確認できます。

>>>

>>> y is x
True

上記のコードは、「+ x 」と「 y 」が同じオブジェクトであることを示しています。 間違いを犯さないでください: ` y +`はまだ不変です。

たとえば、「+ y +」で追加を実行できます。

>>>

>>> y += 1
>>> y is x
False

追加呼び出しの後、新しいPythonオブジェクトが返されます。 これで、メモリは次のようになります。

x名とy名の異なるオブジェクト、幅= 1141、 height = 626

新しいオブジェクトが作成され、 `+ y `が新しいオブジェクトを指すようになりました。 興味深いことに、これは、 ` y `を直接 ` 2339 +`にバインドした場合と同じ最終状態です。

>>>

>>> y = 2339

上記のステートメントは、追加と同じエンドメモリ状態になります。 要約すると、Pythonでは変数を割り当てません。 代わりに、名前を参照にバインドします。

Pythonのインターンオブジェクトに関する注意

Pythonオブジェクトがどのように作成され、名前がそれらのオブジェクトにバインドされるかを理解したので、機械にレンチを投げるときです。 そのレンチは、インターンされたオブジェクトの名前になります。

次のPythonコードがあるとします。

>>>

>>> x = 1000
>>> y = 1000
>>> x is y
True

上記のように、「+ x 」と「 y 」はどちらも同じPythonオブジェクトを指す名前です。 しかし、値「+1000」を保持するPythonオブジェクトは、常に同じメモリアドレスを持つことが保証されているわけではありません。 たとえば、2つの数値を加算して「1000」を取得する場合、異なるメモリアドレスになります。

>>>

>>> x = 1000
>>> y = 499 + 501
>>> x is y
False

今回は、行「+ x is y 」は「 False +」を返します。 これが混乱する場合は、心配しないでください。 このコードが実行されるときに発生するステップは次のとおりです。

  1. Pythonオブジェクトを作成します( + 1000 +

  2. そのオブジェクトに名前「+ x +」を割り当てます

  3. Pythonオブジェクトの作成( + 499 +

  4. Pythonオブジェクトを作成します( + 501 +

  5. これら2つのオブジェクトを一緒に追加します

  6. 新しいPythonオブジェクトを作成します( + 1000 +

  7. そのオブジェクトに名前「+ y +」を割り当てます

*テクニカルノート:*上記の手順は、このコードがREPL内で実行された場合にのみ発生します。 上記の例を使用してファイルに貼り付け、ファイルを実行すると、「+ x is y 」行が「 True +」を返すことがわかります。

これは、コンパイラが賢いために発生します。 CPythonコンパイラはhttps://en.wikipedia.org/wiki/Peephole_optimization[peephole_optimization]と呼ばれる最適化を試みます。これにより、可能な限り実行ステップを節約できます。 詳細については、https://github.com/python/cpython/blob/master/Python/peephole.c [CPythonのピープホールオプティマイザーのソースコード]をご覧ください。

これは無駄ではないですか? ええ、そうです。しかし、それはあなたがPythonのすべての大きな利点に支払う代価です。 これらの中間オブジェクトのクリーンアップについて心配する必要はありません。また、それらが存在することを知る必要さえありません! 喜びは、これらの操作が比較的高速であり、これまでこれらの詳細を知る必要がなかったことです。

コアPython開発者は、賢明にもこの無駄に気付き、いくつかの最適化を行うことにしました。 これらの最適化は、新規参入者にとって驚くべき動作をもたらします。

>>>

>>> x = 20
>>> y = 19 + 1
>>> x is y
True

この例では、今回は結果が `+ True +`であることを除いて、以前とほぼ同じコードが表示されます。 これは、インターンオブジェクトの結果です。 Pythonは、メモリ内のオブジェクトの特定のサブセットを事前に作成し、それらを日常的な使用のためにグローバル名前空間に保持します。

Pythonの実装に依存するオブジェクト。 CPython 3.7は以下をインターンします。

  1. 「+ -5 」と「+256」の間の整数

  2. ASCII文字、数字、またはアンダースコアのみを含む文字列

この背後にある理由は、これらの変数が多くのプログラムで使用される可能性が非常に高いことです。 これらのオブジェクトをインターンすることにより、Pythonは一貫して使用されるオブジェクトのメモリ割り当て呼び出しを防ぎます。

20文字未満で、ASCII文字、数字、またはアンダースコアを含む文字列はインターンされます。 この背後にある理由は、これらが何らかの種類のアイデンティティであると想定されていることです。

>>>

>>> s1 = "realpython"
>>> id(s1)
140696485006960
>>> s2 = "realpython"
>>> id(s2)
140696485006960
>>> s1 is s2
True

ここで、 `+ s1 `と ` s2 +`の両方がメモリ内の同じアドレスを指していることがわかります。 ASCII以外の文字、数字、またはアンダースコアを使用すると、異なる結果が得られます。

>>>

>>> s1 = "Real Python!"
>>> s2 = "Real Python!"
>>> s1 is s2
False

この例には感嘆符( )が含まれているため、これらの文字列はインターンされず、メモリ内の異なるオブジェクトです。

*ボーナス:*これらのオブジェクトに同じ内部オブジェクトを本当に参照させたい場合は、 `+ sys.intern()+`をチェックアウトすることをお勧めします。 この機能の使用例の1つは、ドキュメントで概説されています。

_ 文字列のインターンは、ディクショナリルックアップで少しパフォーマンスを上げるのに役立ちます。ディクショナリのキーがインターンされ、ルックアップキーがインターンされる場合、キー比較(ハッシュ後)は、文字列比較の代わりにポインター比較によって実行できます。 (https://docs.python.org/3/library/sys.html#sys.intern [ソース]) _

インターンされたオブジェクトはしばしば混乱の原因になります。 疑問がある場合は、オブジェクトの等価性を判断するために、常に「+ id()」と「 is +」を使用できることを覚えておいてください。

Pythonでポインタをシミュレートする

Pythonのポインターがネイティブに存在しないからといって、ポインターを使用してもメリットが得られないわけではありません。 実際、Pythonでポインターをシミュレートする方法は複数あります。 このセクションでは2つのことを学びます。

  1. 可変型をポインターとして使用する

  2. カスタムPythonオブジェクトの使用

さて、要点を説明しましょう。

可変型をポインターとして使用する

可変型についてはすでに学習しました。 これらのオブジェクトは可変なので、あたかもポインタであるかのように扱って、ポインタの動作をシミュレートできます。 次のcコードを複製したいとします。

void add_one(int *x) {
    *x += 1;
}

このコードは、整数( + * x +)へのポインターを取り、値を1増やします。 コードを実行する主な機能は次のとおりです。

#include <stdio.h>

int main(void) {
    int y = 2337;
    printf("y = %d\n", y);
    add_one(&y);
    printf("y = %d\n", y);
    return 0;
}

上記のコードでは、「+ 2337+」を「+ y +」に割り当て、現在の値を出力し、値を1増やしてから、変更した値を出力します。 このコードを実行すると、次の出力が得られます。

y = 2337
y = 2338

Pythonでこのタイプの動作を複製する1つの方法は、可変タイプを使用することです。 リストを使用して最初の要素を変更することを検討してください。

>>>

>>> def add_one(x):
...     x[0] += 1
...
>>> y = [2337]
>>> add_one(y)
>>> y[0]
2338

ここで、 `+ add_one(x)`は最初の要素にアクセスし、その値を1つ増やします。 ` list `を使用すると、最終結果によって値が変更されたように見えます。 Pythonのポインターは存在しますか? うーん、ダメ。 これは、 ` list `が可変タイプであるためにのみ可能です。 ` tuple +`を使用しようとすると、エラーが発生します。

>>>

>>> z = (2337,)
>>> add_one(z)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in add_one
TypeError: 'tuple' object does not support item assignment

上記のコードは、 `+ tuple `が不変であることを示しています。 したがって、アイテムの割り当てはサポートされていません。 ` list `だけが可変型ではありません。 Pythonでポインターを模倣する別の一般的なアプローチは、 ` dict +`を使用することです。

興味深いイベントが発生するたびに追跡したいアプリケーションがあるとします。 これを実現する1つの方法は、「+ dict +」を作成し、アイテムの1つをカウンターとして使用することです。

>>>

>>> counters = {"func_calls": 0}
>>> def bar():
...     counters["func_calls"] += 1
...
>>> def foo():
...     counters["func_calls"] += 1
...     bar()
...
>>> foo()
>>> counters["func_calls"]
2

この例では、関数呼び出しの数を追跡するために、 `+ counters `辞書が使用されます。 ` foo()`を呼び出した後、カウンタは予想どおり ` 2 `に増加しました。 すべては、「 dict +」が可変であるためです。

これは、ポインタの動作をシミュレートするだけであり、CまたはC の真のポインタに直接マッピングされないことに注意してください。 つまり、これらの操作は、CやC での操作よりも高価です。

Pythonオブジェクトの使用

`+ dict +`オプションはPythonでポインターをエミュレートする素晴らしい方法ですが、使用したキー名を覚えるのが面倒になる場合があります。 これは、アプリケーションのさまざまな部分で辞書を使用している場合に特に当てはまります。 これは、カスタムPythonクラスが本当に役立つ場所です。

最後の例を基に、アプリケーションのメトリックを追跡すると仮定します。 クラスの作成は、厄介な詳細を抽象化するための優れた方法です。

class Metrics(object):
    def __init__(self):
        self._metrics = {
            "func_calls": 0,
            "cat_pictures_served": 0,
        }

このコードは `+ Metrics `クラスを定義します。 このクラスは、実際のデータを保持するために ` _metrics `メンバー変数にある ` dict +`を引き続き使用します。 これにより、必要な可変性が得られます。 ここで、これらの値にアクセスできる必要があります。 これを行う良い方法の1つは、プロパティを使用することです。

class Metrics(object):
    # ...

    @property
    def func_calls(self):
        return self._metrics["func_calls"]

    @property
    def cat_pictures_served(self):
        return self._metrics["cat_pictures_served"]

このコードはhttps://docs.python.org/3/library/functions.html#property [+ @ property +]を使用します。 デコレータに慣れていない場合は、https://realpython.com/primer-on-python-decorators/[Primer on Python Decorators]をご覧ください。 ここの `+ @ property `デコレータにより、属性であるかのように ` func_calls `と ` cat_pictures_served +`にアクセスできます。

>>>

>>> metrics = Metrics()
>>> metrics.func_calls
0
>>> metrics.cat_pictures_served
0

これらの名前に属性としてアクセスできるという事実は、これらの値が「+ dict +」にあるという事実を抽象化したことを意味します。 また、属性の名前をより明確にします。 もちろん、これらの値を増分できる必要があります。

class Metrics(object):
    # ...

    def inc_func_calls(self):
        self._metrics["func_calls"] += 1

    def inc_cat_pics(self):
        self._metrics["cat_pictures_served"] += 1

次の2つの新しいメソッドを導入しました。

  1. + inc_func_calls()+

  2. + inc_cat_pics()+

これらのメソッドは、メトリック「+ dict +」の値を変更します。 これで、ポインタを変更するかのように変更するクラスができました。

>>>

>>> metrics = Metrics()
>>> metrics.inc_func_calls()
>>> metrics.inc_func_calls()
>>> metrics.func_calls
2

ここでは、アプリケーションのさまざまな場所で `+ func_calls `にアクセスして ` inc_func_calls()+`を呼び出し、Pythonでポインターをシミュレートできます。 これは、アプリケーションのさまざまな部分で頻繁に使用および更新する必要があるメトリックのようなものがある場合に役立ちます。

*注意:*特にこのクラスでは、 `+ @ property.setter `を使用する代わりに ` inc_func_calls()`と ` inc_cat_pics()`を明示的にすると、ユーザーがこれらの値を任意の ` int `に設定できなくなります。または ` dict +`のような無効な値。

`+ Metrics +`クラスの完全なソースは次のとおりです。

class Metrics(object):
    def __init__(self):
        self._metrics = {
            "func_calls": 0,
            "cat_pictures_served": 0,
        }

    @property
    def func_calls(self):
        return self._metrics["func_calls"]

    @property
    def cat_pictures_served(self):
        return self._metrics["cat_pictures_served"]

    def inc_func_calls(self):
        self._metrics["func_calls"] += 1

    def inc_cat_pics(self):
        self._metrics["cat_pictures_served"] += 1

`+ ctypes +`を使用したリアルポインター

さて、多分Python、特にCPythonにはポインターがあります。 組み込みの `+ ctypes `モジュールを使用すると、Pythonで実際のCスタイルポインターを作成できます。 ` ctypes +`に慣れていない場合は、https://dbader.org/blog/python-ctypes-tutorial [Cライブラリと「ctypes」モジュールによるPythonの拡張]をご覧ください。

これを使用する本当の理由は、ポインターを必要とするCライブラリーに対して関数呼び出しを行う必要がある場合です。 前から + add_one()+ C関数に戻りましょう。

void add_one(int *x) {
    *x += 1;
}

ここでも、このコードは `+ x `の値を1つ増やしています。 これを使用するには、まず共有オブジェクトにコンパイルします。 上記のファイルが ` add.c `に保存されていると仮定すると、 ` gcc +`でこれを達成できます。

$ gcc -c -Wall -Werror -fpic add.c
$ gcc -shared -o libadd1.so add.o

最初のコマンドは、Cソースファイルを `+ add.o `というオブジェクトにコンパイルします。 2番目のコマンドは、リンクされていないオブジェクトファイルを受け取り、 ` libadd1.so +`という共有オブジェクトを生成します。

`+ libadd1.so `は現在のディレクトリにあるはずです。 ` ctypes +`を使用してPythonにロードできます:

>>>

>>> import ctypes
>>> add_lib = ctypes.CDLL("./libadd1.so")
>>> add_lib.add_one
<_FuncPtr object at 0x7f9f3b8852a0>

`+ ctypes.CDLL `コードは、 ` libadd1 `共有オブジェクトを表すオブジェクトを返します。 この共有オブジェクトで ` add_one()+`を定義したため、他のPythonオブジェクトであるかのようにアクセスできます。 ただし、関数を呼び出す前に、関数のシグネチャを指定する必要があります。 これにより、Pythonは関数に正しい型を確実に渡すことができます。

この場合、関数シグネチャは整数へのポインターです。 `+ ctypes +`では、次のコードを使用してこれを指定できます。

>>>

>>> add_one = add_lib.add_one
>>> add_one.argtypes = [ctypes.POINTER(ctypes.c_int)]

このコードでは、Cが期待するものと一致するように関数シグネチャを設定しています。 ここで、間違った型でこのコードを呼び出そうとすると、未定義の動作の代わりに素晴らしい警告が表示されます:

>>>

>>> add_one(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 1: <class 'TypeError'>: \
expected LP_c_int instance instead of int

Pythonはエラーをスローし、 `+ add_one()`は整数ではなくポインターを必要としていることを説明します。 幸いなことに、 ` ctypes +`にはこれらの関数にポインターを渡す方法があります。 最初に、Cスタイルの整数を宣言します。

>>>

>>> x = ctypes.c_int()
>>> x
c_int(0)

上記のコードは、値が「0」のCスタイル整数「+ x 」を作成します。 ` ctypes `は、変数を参照渡しできるようにする便利な ` byref()+`を提供します。

注意: by reference という用語は、変数 value を渡すこととは対照的です。

参照渡しの場合、元の変数への参照を渡すため、変更は元の変数に反映されます。 値を渡すと元の変数のコピーが生成され、変更は元の変数に反映されません。

これを使用して、 `+ add_one()+`を呼び出すことができます。

>>>

>>> add_one(ctypes.byref(x))
998793640
>>> x
c_int(1)

いいね! 整数が1増加しました。 おめでとうございます。Pythonで実際のポインターを使用できました。

結論

これで、Pythonオブジェクトとポインターの交差点についての理解が深まりました。 名前と変数の区別のいくつかは重要であるように見えますが、これらの重要な用語を根本的に理解することで、Pythonが変数を処理する方法の理解が深まります。

また、Pythonでポインターをシミュレートする優れた方法をいくつか学びました。

  • 低オーバーヘッドポインターとして可変オブジェクトを利用する

  • 使いやすいカスタムPythonオブジェクトの作成

  • `+ ctypes +`モジュールによる実際のポインターのロック解除

これらのメソッドを使用すると、Pythonが提供するメモリの安全性を犠牲にすることなく、Pythonでポインターをシミュレートできます。

読んでくれてありがとう。 まだ質問がある場合は、コメントセクションまたはTwitterでお気軽にお問い合わせください。