Pythonのポインタ:ポイントは何ですか?
CやC ++のような低レベル言語で作業したことがあるなら、おそらくポインターを聞いたことがあるでしょう。 ポインターを使用すると、コードの一部を非常に効率的に作成できます。 また、初心者にとって混乱を招き、専門家であってもさまざまなメモリ管理バグを引き起こす可能性があります。 Pythonのどこにありますか?また、Pythonでポインターをどのようにシミュレートできますか?
ポインターは、CおよびC ++で広く使用されています。 基本的に、これらは別の変数のメモリアドレスを保持する変数です。 ポインタの復習については、このoverview on C Pointers.を確認することを検討してください。
この記事では、Pythonのオブジェクトモデルをより深く理解し、Pythonのポインターが実際に存在しない理由を学びます。 ポインターの動作を模倣する必要がある場合は、メモリ管理の悪夢を伴わずにPythonでポインターをシミュレートする方法を学習します。
この記事では、次のことを行います。
-
Pythonのポインターが存在しない理由を学ぶ
-
C変数とPython名の違いを調べる
-
Pythonでポインターをシミュレートする
-
ctypes
を使用して実際のポインタを試す
Note:この記事では、「Python」は、CでのPythonのリファレンス実装(CPythonとも呼ばれます)を指します。 この記事では言語の内部について説明しているため、これらの注意事項はCPython 3.7には当てはまりますが、将来または過去の言語の繰り返しでは当てはまらない場合があります。
Free Bonus:Click here to get access to a chapter from Python Tricks: The Bookは、Pythonのベストプラクティスと、より美しい+ Pythonicコードをすぐに適用できる簡単な例を示しています。
Pythonにポインターがないのはなぜですか?
真実は私が知らないということです。 Pythonのポインターはネイティブに存在できますか? おそらく、しかしポインタはZen of Pythonに反するようです。 ポインターは、明示的ではなく暗黙的な変更を推奨します。 多くの場合、特に初心者にとっては、単純ではなく複雑です。 さらに悪いことに、彼らは自分自身を足で撃つ方法を懇願するか、またはあなたが想定されていなかった記憶の一部から読むような本当に危険なことをします。
Pythonは、メモリアドレスなどの実装の詳細をユーザーから抽象化しようとする傾向があります。 Pythonは、速度よりもユーザビリティに重点を置くことがよくあります。 その結果、Pythonのポインターは実際には意味がありません。 恐れることはありませんが、Pythonはデフォルトで、ポインターを使用する利点をいくつか提供します。
Pythonのポインターを理解するには、Pythonの実装の詳細を少し迂回する必要があります。 具体的には、次のことを理解する必要があります。
-
不変オブジェクトと可変オブジェクト
-
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のメモリ管理の内部を詳しく調べるには、Memory Management in Pythonを読むことができます。
型はCPythonレイヤーで使用され、実行時の型の安全性を確保します。 最後に、値があります。これは、オブジェクトに関連付けられている実際の値です。
ただし、すべてのオブジェクトが同じというわけではありません。 理解する必要があるもう1つの重要な違いがあります。不変オブジェクトと可変オブジェクトです。 オブジェクトのタイプの違いを理解することは、Pythonのポインターであるタマネギの最初の層を明確にするのに本当に役立ちます。
不変オブジェクトと可変オブジェクト
Pythonには、2種類のオブジェクトがあります。
-
Immutable objectsは変更できません。
-
Mutable objectsは変更できます。
この違いを理解することは、Pythonでポインターのランドスケープをナビゲートする最初の鍵です。 一般的なタイプの内訳と、それらが可変か不変かを以下に示します。
Type | 不変? |
---|---|
|
Yes |
|
Yes |
|
Yes |
|
Yes |
|
Yes |
|
Yes |
|
Yes |
|
No |
|
No |
|
No |
ご覧のとおり、一般的に使用される多くのプリミティブ型は不変です。 Pythonを書くことで、これを自分で証明できます。 Python標準ライブラリのツールがいくつか必要です。
-
id()
は、オブジェクトのメモリアドレスを返します。 -
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メモリアドレスになります。
Bonus:+=
演算子は様々なメソッド呼び出しに変換される。
list
のような一部のオブジェクトの場合、+=
は__iadd__()
に変換されます(インプレース追加)。 これにより、self
が変更され、同じIDが返されます。 ただし、str
とint
にはこれらのメソッドがないため、__iadd__()
ではなく__add__()
が呼び出されます。
詳細については、Pythonhttps://docs.python.org/3/reference/datamodel.html#object。iadd [データモデルドキュメント]をご覧ください。
文字列s
を直接変更しようとすると、エラーが発生します。
>>>
>>> s[0] = "R"
Traceback (most recent call last):
File "", line 1, in
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
はsameIDを持ちます。 これは、list
タイプが変更可能であるためです。
リストが変更可能であることを示す別の方法は、割り当てを使用することです。
>>>
>>> my_list[0] = 0
>>> my_list
[0, 2, 3, 4]
>>> id(my_list)
140637819575368
このコードでは、my_list
を変更し、最初の要素を0
に設定します。 ただし、この割り当て後も同じIDを維持します。 可変オブジェクトと不変オブジェクトが邪魔にならないように、Python enlightenmentへの旅の次のステップは、Pythonの可変エコシステムを理解することです。
変数を理解する
Python変数は、CまたはC ++の変数と根本的に異なります。 実際、Pythonには変数さえありません。 Python has names, not variables.
これはつまらなく見えるかもしれませんが、ほとんどの場合そうです。 ほとんどの場合、Pythonの名前を変数として考えることは完全に受け入れられますが、違いを理解することは重要です。 これは、Pythonでポインターのトリッキーな主題をナビゲートしている場合に特に当てはまります。
違いを理解しやすくするために、Cで変数がどのように機能するか、それらが何を表すかを見て、それをPythonでの名前の機能と比較することができます。
Cの変数
変数x
を定義する次のコードがあるとします。
int x = 2337;
この1行のコードには、実行時にいくつかの明確なステップがあります。
-
整数に十分なメモリを割り当てます
-
そのメモリ位置に値
2337
を割り当てます -
x
がその値を指していることを示します
メモリを簡略化して表示すると、次のようになります。
ここで、変数x
の偽のメモリ位置が0x7f1
で、値が2337
であることがわかります。 プログラムの後半でx
の値を変更したい場合は、次のようにすることができます。
x = 2338;
上記のコードは、変数x
に新しい値(2338
)を割り当て、それによってoverwritingが前の値になります。 これは、変数x
がmutableであることを意味します。 更新されたメモリレイアウトには、新しい値が表示されます。
x
の場所は変更されておらず、値自体のみが変更されていることに注意してください。 これは重要なポイントです。 これは、名前だけでなく、x
is the memory locationを意味します。
この概念を考えるもう1つの方法は、所有権の観点です。 ある意味では、x
がメモリ位置を所有します。 x
は、最初は、整数値を格納できる1つの整数に正確に収まる空のボックスです。
x
に値を割り当てると、x
が所有するボックスに値が配置されます。 新しい変数(y
)を導入したい場合は、次のコード行を追加できます。
int y = x;
このコードは、y
というnewボックスを作成し、値をx
からボックスにコピーします。 これで、メモリレイアウトは次のようになります。
y
の新しい場所0x7f5
に注目してください。 x
の値がy
にコピーされた場合でも、変数y
はメモリ内に新しいアドレスを所有しています。 したがって、x
に影響を与えることなく、y
の値を上書きできます。
y = 2339;
これで、メモリレイアウトは次のようになります。
ここでも、y
の値を変更しましたが、notはその場所です。 さらに、元のx
変数にはまったく影響していません。 これは、Pythonの名前がどのように機能するかとはまったく対照的です。
Pythonの名前
Pythonには変数がありません。 名前があります。 はい、これは重要な点であり、変数という用語は好きなだけ使用できます。 変数と名前には違いがあることを知っておくことが重要です。
上記のCの例から同等のコードを取得して、Pythonで記述しましょう。
>>>
>>> x = 2337
Cの場合と同様に、上記のコードは実行中にいくつかの異なるステップに分割されます。
-
PyObject
を作成します -
PyObject
のタイプコードを整数に設定します -
PyObject
の値を2337
に設定します -
x
という名前を作成します -
x
を新しいPyObject
にポイントします -
PyObject
のrefcountを1増やします
Note:PyObject
はPythonのobject
と同じではありません。 CPythonに固有であり、すべてのPythonオブジェクトの基本構造を表します。
PyObject
はC構造体として定義されているため、typecode
またはrefcount
を直接呼び出すことができない理由がわからない場合は、構造体に直接アクセスできないためです。 sys.getrefcount()
のようなメソッド呼び出しは、いくつかの内部を取得するのに役立ちます。
メモリ内では、次のようになります。
メモリレイアウトは、以前のCレイアウトとは大きく異なることがわかります。 値2337
が存在するメモリブロックをx
が所有する代わりに、新しく作成されたPythonオブジェクトが2337
が存在するメモリを所有します。 Python名x
は、C変数x
がメモリ内の静的スロットを所有していたように、anyメモリアドレスを直接所有していません。
x
に新しい値を割り当てようとする場合は、次のことを試すことができます。
>>>
>>> x = 2338
ここで起こっていることは、Cの同等のものとは異なりますが、Pythonの元のバインドとあまり違いはありません。
このコード:
-
新しい
PyObject
を作成します -
PyObject
のタイプコードを整数に設定します -
PyObject
の値を2338
に設定します -
x
が新しいPyObject
を指す -
新しい
PyObject
のrefcountを1増やします -
古い
PyObject
のrefcountを1つ減らします
メモリー内では、次のようになります。
この図は、x
がオブジェクトへの参照を指し、以前のようにメモリ空間を所有していないことを示すのに役立ちます。 また、x = 2338
コマンドが割り当てではなく、名前x
を参照にバインドしていることも示しています。
さらに、前のオブジェクト(2337
値を保持していた)は現在、参照カウント0でメモリ内にあり、garbage collectorによってクリーンアップされます。
Cの例のように、新しい名前y
をミックスに導入できます。
>>>
>>> y = x
メモリには、新しい名前が付けられますが、必ずしも新しいオブジェクトではありません。
これで、新しいPythonオブジェクトにnotが作成されたことがわかります。これは、同じオブジェクトを指す新しい名前だけです。 また、オブジェクトの参照カウントが1つ増えました。 オブジェクトの同一性をチェックして、それらが同じであることを確認できます。
>>>
>>> y is x
True
上記のコードは、x
とy
が同じオブジェクトであることを示しています。 ただし、間違いはありません。y
は依然として不変です。
たとえば、y
で加算を実行できます。
>>>
>>> y += 1
>>> y is x
False
追加呼び出しの後、新しいPythonオブジェクトが返されます。 これで、メモリは次のようになります。
新しいオブジェクトが作成され、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
を返します。 これが混乱する場合は、心配しないでください。 このコードが実行されるときに発生するステップは次のとおりです。
-
Pythonオブジェクトを作成します(
1000
) -
そのオブジェクトに名前
x
を割り当てます -
Pythonオブジェクトを作成します(
499
) -
Pythonオブジェクトを作成します(
501
) -
これら2つのオブジェクトを一緒に追加します
-
新しいPythonオブジェクトを作成します(
1000
) -
そのオブジェクトに名前
y
を割り当てます
Technical Note:上記の手順は、このコードがREPL内で実行された場合にのみ発生します。 上記の例を使用してファイルに貼り付け、ファイルを実行すると、x is y
行がTrue
を返すことがわかります。
これは、コンパイラが賢いために発生します。 CPythonコンパイラは、peephole optimizationsと呼ばれる最適化を試みます。これは、可能な限り実行ステップを節約するのに役立ちます。 詳細については、CPython’s peephole optimizer source codeを確認してください。
これは無駄ではないですか? ええ、そうです。しかし、それはあなたがPythonのすべての大きな利点に支払う代価です。 これらの中間オブジェクトのクリーンアップについて心配する必要はありません。また、それらが存在することを知る必要さえありません! 喜びは、これらの操作が比較的高速であり、これまでこれらの詳細を知る必要がなかったことです。
コアPython開発者は、賢明にもこの無駄に気付き、いくつかの最適化を行うことにしました。 これらの最適化は、新規参入者にとって驚くべき動作をもたらします。
>>>
>>> x = 20
>>> y = 19 + 1
>>> x is y
True
この例では、結果がTrue
であることを除いて、以前とほぼ同じコードが表示されます。 これは、インターンオブジェクトの結果です。 Pythonは、メモリ内のオブジェクトの特定のサブセットを事前に作成し、それらを日常的な使用のためにグローバル名前空間に保持します。
Pythonの実装に依存するオブジェクト。 CPython 3.7は以下をインターンします。
-
-5
と256
の間の整数 -
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
この例には感嘆符(!
)が含まれているため、これらの文字列はインターンされておらず、メモリ内の異なるオブジェクトです。
Bonus:これらのオブジェクトが本当に同じ内部オブジェクトを参照するようにしたい場合は、sys.intern()
をチェックアウトすることをお勧めします。 この機能の使用例の1つは、ドキュメントで概説されています。
文字列のインターンは、ディクショナリルックアップで少しパフォーマンスを上げるのに役立ちます。ディクショナリのキーがインターンされ、ルックアップキーがインターンされる場合、キー比較(ハッシュ後)は、文字列比較の代わりにポインター比較によって実行できます。 (Source)
インターンされたオブジェクトはしばしば混乱の原因になります。 疑問がある場合は、いつでもid()
とis
を使用してオブジェクトの同等性を判断できることを覚えておいてください。
Pythonでポインタをシミュレートする
Pythonのポインターがネイティブに存在しないからといって、ポインターを使用してもメリットが得られないわけではありません。 実際、Pythonでポインターをシミュレートする方法は複数あります。 このセクションでは2つのことを学びます。
-
可変型をポインターとして使用する
-
カスタムPythonオブジェクトの使用
さて、要点を説明しましょう。
可変型をポインターとして使用する
可変型についてはすでに学習しました。 これらのオブジェクトは可変なので、あたかもポインタであるかのように扱って、ポインタの動作をシミュレートできます。 次のcコードを複製したいとします。
void add_one(int *x) {
*x += 1;
}
このコードは、整数(*x
)へのポインターを受け取り、値を1つインクリメントします。 コードを実行する主な機能は次のとおりです。
#include
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 "", line 1, in
File "", line 2, in add_one
TypeError: 'tuple' object does not support item assignment
上記のコードは、tuple
が不変であることを示しています。 したがって、アイテムの割り当てはサポートされていません。 list
だけが可変タイプではありません。 Pythonでポインタを模倣するもう1つの一般的なアプローチは、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
は可変であるためです。
これはsimulatesのポインターの動作にすぎず、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"]
このコードは@property
を利用します。 デコレータに慣れていない場合は、この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つの新しいメソッドを導入しました。
-
inc_func_calls()
-
inc_cat_pics()
これらのメソッドは、メトリックdict
の値を変更します。 これで、ポインタを変更するかのように変更するクラスができました。
>>>
>>> metrics = Metrics()
>>> metrics.inc_func_calls()
>>> metrics.inc_func_calls()
>>> metrics.func_calls
2
ここでは、アプリケーションのさまざまな場所でfunc_calls
にアクセスし、inc_func_calls()
を呼び出して、Pythonでポインターをシミュレートできます。 これは、アプリケーションのさまざまな部分で頻繁に使用および更新する必要があるメトリックのようなものがある場合に役立ちます。
Note:特にこのクラスでは、@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
に慣れていない場合は、Extending Python With C Libraries and the “ctypes” Moduleを確認できます。
これを使用する本当の理由は、ポインターを必要とする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 "", line 1, in
ctypes.ArgumentError: argument 1: : \
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()
を提供します。
Note:用語by referenceは、変数by valueを渡すこととは反対です。
参照渡しの場合、元の変数への参照を渡すため、変更は元の変数に反映されます。 値を渡すと元の変数のコピーが生成され、変更は元の変数に反映されません。
これを使用して、add_one()
を呼び出すことができます。
>>>
>>> add_one(ctypes.byref(x))
998793640
>>> x
c_int(1)
いいね! 整数が1増加しました。 おめでとうございます。Pythonで実際のポインターを使用できました。
結論
これで、Pythonオブジェクトとポインターの交差点についての理解が深まりました。 名前と変数の区別のいくつかは重要であるように見えますが、これらの重要な用語を根本的に理解することで、Pythonが変数を処理する方法の理解が深まります。
また、Pythonでポインターをシミュレートする優れた方法をいくつか学びました。
-
低オーバーヘッドポインターとして可変オブジェクトを利用する
-
使いやすいカスタムPythonオブジェクトの作成
-
ctypes
モジュールで実際のポインタのロックを解除する
これらのメソッドを使用すると、Pythonが提供するメモリの安全性を犠牲にすることなく、Pythonでポインターをシミュレートできます。
読んでくれてありがとう。 まだ質問がある場合は、コメントセクションまたはTwitterでお気軽にお問い合わせください。