Python/クロージャ
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[Pythonを読む]]
#contents
*はじめに [#k3f522bc]
クロージャの処理、具体的には外側の変数がどう扱われている...
#code(Python){{
def outer(a):
def inner(b):
return a + b
return inner
}}
と書いたときのa、つまり、innerから見ると自分で定義してな...
*compile, disを使っての確認 [#ud8cea74]
ではcompile関数とdisモジュールを使ってコードオブジェクト...
>>> src = '''
def outer(a):
def inner(b):
return a + b
return inner
'''
>>> co = compile(src, '<string>', 'exec')
>>> co.co_consts
(<code object outer at 0x0250C3E8, file "<string>", line...
**outer関数 [#m865cc96]
outerのバイトコードを表示
>>> dis.dis(co.co_consts[0])
3 0 LOAD_CLOSURE 0 (a)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (<code object...
6 LOAD_CONST 2 ('outer.<loca...
8 MAKE_FUNCTION 8
10 STORE_FAST 1 (inner)
5 12 LOAD_FAST 1 (inner)
14 RETURN_VALUE
さっそくクロージャという単語が出てきました。
innerに進む前にouterのコードオブジェクトについてもう少し...
>>> co.co_consts[0].co_cellvars
('a',)
>>> co.co_consts[0].co_freevars
()
cellvarsにaが記録されています。
**inner関数 [#ba8f2c14]
では、innerに入りましょう。
>>> dis.dis(co.co_consts[0].co_consts[1])
4 0 LOAD_DEREF 0 (a)
2 LOAD_FAST 0 (b)
4 BINARY_ADD
6 RETURN_VALUE
LOAD_DEREFという見慣れない命令が使われています。
co_cellvarsとco_freevarsを確認します。
>>> co.co_consts[0].co_consts[1].co_cellvars
()
>>> co.co_consts[0].co_consts[1].co_freevars
('a',)
今度はfreevarsにaが記録されています。このことから
-freevarとはコード内で使ってるんだけどコード中で定義され...
-cellvarとはクロージャで使われてる変数
と考えることができます。
ちなみに、引数で渡されてるけどクロージャでは使われていな...
#code(Python){{
c = 3
def outer(a, d):
def inner(b):
return a + b + c
return inner
}}
「外側の変数」とグローバル変数は明確に区別されているよう...
ここら辺の処理はAST作った後のシンボルテーブル作成で行われ...
*クロージャ作る側の処理 [#h233728e]
では本編に入りましょう。クロージャを作る側で関係ありそう...
3 0 LOAD_CLOSURE 0 (a)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (<code object...
6 LOAD_CONST 2 ('outer.<loca...
8 MAKE_FUNCTION 8
それぞれ見てみる。
**LOAD_CLOSURE周りの処理 [#kdc33e80]
#code(C){{
TARGET(LOAD_CLOSURE) {
PyObject *cell = freevars[oparg];
Py_INCREF(cell);
PUSH(cell);
DISPATCH();
}
}}
freevarsが何者なのか確認。念のためですが以下のコードはポ...
#code(C){{
freevars = f->f_localsplus + co->co_nlocals;
}}
localsplusはローカル変数+スタック領域なはずでした。さら...
#code(C){{
Py_ssize_t extras, ncells, nfrees;
ncells = PyTuple_GET_SIZE(code->co_cellvars);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
extras = code->co_stacksize + code->co_nlocals + ...
nfrees;
if (free_list == NULL) {
f = PyObject_GC_NewVar(PyFrameObject, &PyFram...
extras);
}}
今の場合、ncellsは1でnfreesは0、nlocalsは2(aとinner)で...
ところでcellの領域っていつ初期化されるの?ってところです...
#code(C){{
/* Allocate and initialize storage for cell vars, and...
vars into frame. */
for (i = 0; i < PyTuple_GET_SIZE(co->co_cellvars); ++...
PyObject *c;
int arg;
/* Possibly account for the cell variable being a...
if (co->co_cell2arg != NULL &&
(arg = co->co_cell2arg[i]) != CO_CELL_NOT_AN_...
c = PyCell_New(GETLOCAL(arg));
/* Clear the local copy. */
SETLOCAL(arg, NULL);
}
else {
c = PyCell_New(NULL);
}
if (c == NULL)
goto fail;
SETLOCAL(co->co_nlocals + i, c);
}
}}
先ほど、nlocalsは2でaが含まれていると言いましたが、cellva...
コメントにあるようにすぐ下でfreevarsも設定していますがそ...
**MAKE_FUNCTIONでの処理 [#r4742ae2]
MAKE_FUNCTIONで今までと違うのはopargが8になっている点です。
#code(C){{
TARGET(MAKE_FUNCTION) {
PyObject *qualname = POP();
PyObject *codeobj = POP();
PyFunctionObject *func = (PyFunctionObject *)
PyFunction_NewWithQualName(codeobj, f->f_...
Py_DECREF(codeobj);
Py_DECREF(qualname);
if (func == NULL) {
goto error;
}
if (oparg & 0x08) {
assert(PyTuple_CheckExact(TOP()));
func ->func_closure = POP();
}
// 省略
PUSH((PyObject *)func);
DISPATCH();
}
}}
見ての通り、積まれているタプルをfunc_closureという領域に...
'outer.<locals>.inner'
innerのコードオブジェクト
(PyCell(a),)
というわけでMAKE_FUNCTION実行時点のaの値が保存されたとい...
*クロージャ側の処理 [#l9e8b05e]
ここまでわかれば参照側はわかったようなものですが
4 0 LOAD_DEREF 0 (a)
LOAD_DEREFの処理部分
#code(C){{
TARGET(LOAD_DEREF) {
PyObject *cell = freevars[oparg];
PyObject *value = PyCell_GET(cell);
if (value == NULL) {
format_exc_unbound(co, oparg);
goto error;
}
Py_INCREF(value);
PUSH(value);
DISPATCH();
}
}}
先ほど後で見ると言ったfreevarsの設定を確認しましょう。
#code(C){{
/* Copy closure variables to free variables */
for (i = 0; i < PyTuple_GET_SIZE(co->co_freevars); ++...
PyObject *o = PyTuple_GET_ITEM(closure, i);
Py_INCREF(o);
freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] =...
}
}}
closureは先ほど保存したタプルです。呼び出し部分を見ていま...
*おわりに [#a47e2bd0]
今回はクロージャの処理について見てきました。Rubyだとこの...
クロージャを作る瞬間の変数情報(変数が指しているオブジェ...
終了行:
[[Pythonを読む]]
#contents
*はじめに [#k3f522bc]
クロージャの処理、具体的には外側の変数がどう扱われている...
#code(Python){{
def outer(a):
def inner(b):
return a + b
return inner
}}
と書いたときのa、つまり、innerから見ると自分で定義してな...
*compile, disを使っての確認 [#ud8cea74]
ではcompile関数とdisモジュールを使ってコードオブジェクト...
>>> src = '''
def outer(a):
def inner(b):
return a + b
return inner
'''
>>> co = compile(src, '<string>', 'exec')
>>> co.co_consts
(<code object outer at 0x0250C3E8, file "<string>", line...
**outer関数 [#m865cc96]
outerのバイトコードを表示
>>> dis.dis(co.co_consts[0])
3 0 LOAD_CLOSURE 0 (a)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (<code object...
6 LOAD_CONST 2 ('outer.<loca...
8 MAKE_FUNCTION 8
10 STORE_FAST 1 (inner)
5 12 LOAD_FAST 1 (inner)
14 RETURN_VALUE
さっそくクロージャという単語が出てきました。
innerに進む前にouterのコードオブジェクトについてもう少し...
>>> co.co_consts[0].co_cellvars
('a',)
>>> co.co_consts[0].co_freevars
()
cellvarsにaが記録されています。
**inner関数 [#ba8f2c14]
では、innerに入りましょう。
>>> dis.dis(co.co_consts[0].co_consts[1])
4 0 LOAD_DEREF 0 (a)
2 LOAD_FAST 0 (b)
4 BINARY_ADD
6 RETURN_VALUE
LOAD_DEREFという見慣れない命令が使われています。
co_cellvarsとco_freevarsを確認します。
>>> co.co_consts[0].co_consts[1].co_cellvars
()
>>> co.co_consts[0].co_consts[1].co_freevars
('a',)
今度はfreevarsにaが記録されています。このことから
-freevarとはコード内で使ってるんだけどコード中で定義され...
-cellvarとはクロージャで使われてる変数
と考えることができます。
ちなみに、引数で渡されてるけどクロージャでは使われていな...
#code(Python){{
c = 3
def outer(a, d):
def inner(b):
return a + b + c
return inner
}}
「外側の変数」とグローバル変数は明確に区別されているよう...
ここら辺の処理はAST作った後のシンボルテーブル作成で行われ...
*クロージャ作る側の処理 [#h233728e]
では本編に入りましょう。クロージャを作る側で関係ありそう...
3 0 LOAD_CLOSURE 0 (a)
2 BUILD_TUPLE 1
4 LOAD_CONST 1 (<code object...
6 LOAD_CONST 2 ('outer.<loca...
8 MAKE_FUNCTION 8
それぞれ見てみる。
**LOAD_CLOSURE周りの処理 [#kdc33e80]
#code(C){{
TARGET(LOAD_CLOSURE) {
PyObject *cell = freevars[oparg];
Py_INCREF(cell);
PUSH(cell);
DISPATCH();
}
}}
freevarsが何者なのか確認。念のためですが以下のコードはポ...
#code(C){{
freevars = f->f_localsplus + co->co_nlocals;
}}
localsplusはローカル変数+スタック領域なはずでした。さら...
#code(C){{
Py_ssize_t extras, ncells, nfrees;
ncells = PyTuple_GET_SIZE(code->co_cellvars);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
extras = code->co_stacksize + code->co_nlocals + ...
nfrees;
if (free_list == NULL) {
f = PyObject_GC_NewVar(PyFrameObject, &PyFram...
extras);
}}
今の場合、ncellsは1でnfreesは0、nlocalsは2(aとinner)で...
ところでcellの領域っていつ初期化されるの?ってところです...
#code(C){{
/* Allocate and initialize storage for cell vars, and...
vars into frame. */
for (i = 0; i < PyTuple_GET_SIZE(co->co_cellvars); ++...
PyObject *c;
int arg;
/* Possibly account for the cell variable being a...
if (co->co_cell2arg != NULL &&
(arg = co->co_cell2arg[i]) != CO_CELL_NOT_AN_...
c = PyCell_New(GETLOCAL(arg));
/* Clear the local copy. */
SETLOCAL(arg, NULL);
}
else {
c = PyCell_New(NULL);
}
if (c == NULL)
goto fail;
SETLOCAL(co->co_nlocals + i, c);
}
}}
先ほど、nlocalsは2でaが含まれていると言いましたが、cellva...
コメントにあるようにすぐ下でfreevarsも設定していますがそ...
**MAKE_FUNCTIONでの処理 [#r4742ae2]
MAKE_FUNCTIONで今までと違うのはopargが8になっている点です。
#code(C){{
TARGET(MAKE_FUNCTION) {
PyObject *qualname = POP();
PyObject *codeobj = POP();
PyFunctionObject *func = (PyFunctionObject *)
PyFunction_NewWithQualName(codeobj, f->f_...
Py_DECREF(codeobj);
Py_DECREF(qualname);
if (func == NULL) {
goto error;
}
if (oparg & 0x08) {
assert(PyTuple_CheckExact(TOP()));
func ->func_closure = POP();
}
// 省略
PUSH((PyObject *)func);
DISPATCH();
}
}}
見ての通り、積まれているタプルをfunc_closureという領域に...
'outer.<locals>.inner'
innerのコードオブジェクト
(PyCell(a),)
というわけでMAKE_FUNCTION実行時点のaの値が保存されたとい...
*クロージャ側の処理 [#l9e8b05e]
ここまでわかれば参照側はわかったようなものですが
4 0 LOAD_DEREF 0 (a)
LOAD_DEREFの処理部分
#code(C){{
TARGET(LOAD_DEREF) {
PyObject *cell = freevars[oparg];
PyObject *value = PyCell_GET(cell);
if (value == NULL) {
format_exc_unbound(co, oparg);
goto error;
}
Py_INCREF(value);
PUSH(value);
DISPATCH();
}
}}
先ほど後で見ると言ったfreevarsの設定を確認しましょう。
#code(C){{
/* Copy closure variables to free variables */
for (i = 0; i < PyTuple_GET_SIZE(co->co_freevars); ++...
PyObject *o = PyTuple_GET_ITEM(closure, i);
Py_INCREF(o);
freevars[PyTuple_GET_SIZE(co->co_cellvars) + i] =...
}
}}
closureは先ほど保存したタプルです。呼び出し部分を見ていま...
*おわりに [#a47e2bd0]
今回はクロージャの処理について見てきました。Rubyだとこの...
クロージャを作る瞬間の変数情報(変数が指しているオブジェ...
ページ名: