2017-07-10 16 views
15

私は同僚のために、GILの背後にある基本的な行動や推論を説明するためのプレゼンテーションに取り組んできました。新しく宣言された変数には、私が期待するものではなく、4つの参照があるようです。例えば、以下のコード:この出力でPythonで新しく作成された変数のref-countが4であるのはなぜですか?

the_var = 'Hello World!' 
print('Var created: {} references'.format(sys.getrefcount(the_var))) 

結果:

Var created: 4 references 

私は整数を使用した場合の出力が同じであったことを検証> 100(< 100は、予め作成され、 ref-countが大きい)または浮動小数点数、関数スコープ内またはループ内で変数を宣言した場合結果は同じでした。この行動は2.7.11と3.5.1でも同じように見える。

sys.getrefcountをデバッグして、追加の参照を作成していたかどうかを確認しようとしましたが、関数にステップインすることができませんでした(私はそれがCレイヤーの下の直接のサンクであると仮定しています)。

私はプレゼントでこれについて少なくとも1つの質問が出ることを知っていますが、とにかく実際には出力に困惑しています。誰も私にこの行動を説明することはできますか?

+0

これを直接pythonまたはipythonシェルに入力していませんか?3の代わりに4の代わりになるでしょう。またはスタンドアロンスクリプトとして実行していますか?私は単純なPythonシェルで2つの参照を取得します。 – Meitham

+0

私はあなたのコードを2.7.13で実行すると 'Var created:2 references'を取得します。 – Alex

+0

私は対話モードでは2つの参照を、スクリプトでは3つ(コードオブジェクトの定数からの参照のために1つ)を追加する予定ですが、別の参照を作成することがあります。 – user2357112

答えて

13

異なる参照カウントを生成するシナリオがいくつかあります。最も簡単にはREPLコンソールです:この結果を理解する

>>> import sys 
>>> the_var = 'Hello World!' 
>>> print(sys.getrefcount(the_var)) 
2 

は非常に簡単です - The count returned is generally one higher than you might expect - 1つのローカルスタック内の参照とsys.getrefcount()機能(さえdocumentationはそれについて警告に別の一時/ローカルがあります)。スタンドアロンスクリプトとして実行するときしかし:あなたが気づいたよう

import sys 

the_var = 'Hello World!' 
print(sys.getrefcount(the_var)) 
# 4 

、あなたは4を取得します。だから何を与える?

>>> import gc 
>>> the_var = 'Hello World!' 
>>> gc.get_referrers(the_var) 
[{'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'the_var': 'Hello 
World!', 'gc': <module 'gc' (built-in)>, '__name__': '__main__', '__doc__': None}] 

ませ不思議が、 - それは、本質的に単に現在のネームスペースです:私たちはREPLコンソールでそれを実行していない場合はそう - gcモジュール - まあ、ガベージコレクタに非常に有用インターフェースがあります...調査することができます(locals())は、他の場所には存在しません。

import gc 
import pprint 

the_var = 'Hello World!' 
pprint.pprint(gc.get_referrers(the_var)) 

これは(YMMV、あなたのPythonのバージョンに基づく)を出力します:

[['gc', 
    'pprint', 
    'the_var', 
    'Hello World!', 
    'pprint', 
    'pprint', 
    'gc', 
    'get_referrers', 
    'the_var'], 
(-1, None, 'Hello World!'), 
{'__builtins__': <module '__builtin__' (built-in)>, 
    '__doc__': None, 
    '__file__': 'test.py', 
    '__name__': '__main__', 
    '__package__': None, 
    'gc': <module 'gc' (built-in)>, 
    'pprint': <module 'pprint' from 'D:\Dev\Python\Py27-64\lib\pprint.pyc'>, 
    'the_var': 'Hello World!'}] 

案の定、我々はリストに2つの参照を持っている私たちは、スタンドアロンスクリプトとしてという実行したときはどうなりますsys.getrefcount()が私たちに語ったのと同じように、それらは何ですか?さて、Pythonインタプリタがあなたのスクリプトを解析しているときには、最初にcompileバイトコードにする必要があります。そして、それが実行されている間は、変数にも言及しているため、参照として宣言されているリストにすべての文字列を格納します。

もう1つの潜在的なエントリ((-1, None, 'Hello World!'))はpeep-hole optimizerから来ており、アクセス(この場合は文字列参照)が最適化されています。これらの

の両方が純粋に一時的なオプションである - あなたの現在のコンテキストから、あなたのコンパイル「を外部委託」していた場合は、これらの参照が表示されないので、REPLコンソールは、コンテキスト分離を行っている。

import gc 
import pprint 

exec(compile("the_var = 'Hello World!'", "<string>", "exec")) 
pprint.pprint(gc.get_referrers(the_var)) 

あなたは「取得dは:

import sys 

exec(compile("the_var = 'Hello World!'", "<string>", "exec")) 
print(sys.getrefcount(the_var)) 
# 2 

[{'__builtins__': <module '__builtin__' (built-in)>, 
    '__doc__': None, 
    '__file__': 'test.py', 
    '__name__': '__main__', 
    '__package__': None, 
    'gc': <module 'gc' (built-in)>, 
    'pprint': <module 'pprint' from 'D:\Dev\Python\Py27-64\lib\pprint.pyc'>, 
    'the_var': 'Hello World!'}] 

を、あなたはsys.getreferencecount()経由で参照カウントを得ることで、元の試みに戻っていた場合

REPLコンソールのように、まるで期待通りです。ピープホールの最適化による余分な参照は、内部で発生するため、参照を数える前にガベージコレクション(gc.collect())を強制してすぐに破棄することができます。

ただし、コンパイル時に作成される文字列リストは、ファイル全体が解析されてコンパイルされるまで解放することはできません。そのため、別のスクリプトでスクリプトをインポートしてから参照番号をthe_var

+0

あなたが見ている出力、 '4'、または余分な' gc.get_referrers() 'エントリを再現できません。このコードを実行するためにあなたは何を使用していますか? – user2357112

+0

@ user2357112 - 最新のCPythonバージョンで何かが変更されていない限り、これは標準の 'python test_script.py'実行時のデフォルトの動作でなければなりません。もちろん、あなたのスクリプトをPYCにコンパイルして実行すると、 '4'が得られません - 余分な参照はコンパイル/最適化のためです。最新のCPythonソースを見ていきますが、これはかなり変わったとは思いません。 – zwer

+0

3.6でそれを試してみると、Ideonが3.5だったのを忘れてしまったので、 '(-1、None、 'Hello World!')'タプルではなく '4'とリストが再現されます。 – user2357112

関連する問題