UP
デコレータ
前のページへ
Python
次のページへ
オブジェクトの情報を探る(1) - イントロスペクション


関数オブジェクトと属性

【 目次 】

関数もオブジェクト

pythonの関数はオブジェクト。
関数の呼び出しは関数名の後に引数をカッコでくくって記述するが

# 関数の定義
def add_func(a,b):
    return a+b

# 関数呼び出し
result=add_func(2,3)
print result, type(result)

実行結果

5 <type 'int'>

関数呼び出され、関数の結果が返ってくる。

関数名をカッコ無しで記述すると、それは関数オブジェクトを指す。

# 関数オブジェクトの代入
result=add_func
print result, type(result)

実行結果

<function add_func at 0x024BB130> <type 'function'>

上記のコードは関数オブジェクトfuncを変数resultに代入したことになる。

そして、defによる関数add_funcの定義は、functionクラスのインスタンスadd_funcを生成していると解釈する事もできる。

types.FunctionType

関数の型がfunction型だからといって、functionというキーワードを直接コードに記述する事はできない。

print vars(function)

実行結果

Traceback (most recent call last):
  File "src", line 9, in <module>
    print vars(function)
NameError: name 'function' is not defined

上記のコードはfunction型にどのような属性が定義されているか確認しようとしたわけであるがfunctionという名前は定義されていないと怒られてしまった。
function型をコードに記述する場合は、かわりにtypesモジュールに定義されているFunctionTypeを使う。

ついでにdir関数も使って、function型を使ってどのような属性にアクセスできるのかを表示してみた。

import types

print vars(types.FunctionType)
print dir(types.FunctionType)

実行結果

{'func_closure': <member 'func_closure' of 'function' objects>, '__module__': <member '__module__' of 'function' objects>, '__getattribute__': <slot wrapper '__getattribute__' of 'function' objects>, '__dict__': <attribute '__dict__' of 'function' objects>, '__code__': <attribute '__code__' of 'function' objects>, 'func_code': <attribute 'func_code' of 'function' objects>, '__setattr__': <slot wrapper '__setattr__' of 'function' objects>, '__new__': <built-in method __new__ of type object at 0x000000001E2936B0>, '__closure__': <member '__closure__' of 'function' objects>, '__call__': <slot wrapper '__call__' of 'function' objects>, '__get__': <slot wrapper '__get__' of 'function' objects>, '__doc__': <member '__doc__' of 'function' objects>, 'func_dict': <attribute 'func_dict' of 'function' objects>, 'func_name': <attribute 'func_name' of 'function' objects>, '__name__': <attribute '__name__' of 'function' objects>, 'func_globals': <member 'func_globals' of 'function' objects>, '__defaults__': <attribute '__defaults__' of 'function' objects>, '__globals__': <member '__globals__' of 'function' objects>, '__delattr__': <slot wrapper '__delattr__' of 'function' objects>, 'func_defaults': <attribute 'func_defaults' of 'function' objects>, '__repr__': <slot wrapper '__repr__' of 'function' objects>, 'func_doc': <member 'func_doc' of 'function' objects>}
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

同様に、オブジェクトが関数型のインスタンスかどうか調べるにはtypes.FunctionTypeを使って、

print isinstance(add_func, types.FunctionType)

実行結果

True

関数オブジェクトの特殊属性

関数オブジェクトは呼び出し可能型 (callable type)の中のユーザ定義関数に分類される。

呼び出し可能型には関数の他にもメソッドクラス特殊メソッド__call__ を実装したクラスのインスタンスも含まれる。

クラスやインスタンスの属性については「クラスとインスタンスの情報を取得する」 などで,メソッドの属性については「メソッドオブジェクト(2)」 で既に述べた。

ここでは、呼び出し可能型のなかでも、関数に的を絞って,関数の属性に関して述べていくことにする。


関数もオブジェクトなので特殊属性をもっている。

print dir(add_func)

実行結果

['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

以下に特殊属性の一覧を公式ドキュメントより引用する。

属性 意味
__doc__
func_doc
関数のドキュメンテーション文字列です。ドキュメンテーションがない場合は None になります。 書き込み可能
__name__
func_name
関数の名前です。 書き込み可能
__module__ 関数が定義されているモジュールの名前です。モジュール名がない場合は None になります。 書き込み可能
__defaults__
func_defaults
デフォルト値を持つ引数に対するデフォルト値が収められたタプルで、デフォルト値を持つ引数がない場合には None になります 書き込み可能
__code__
func_code
コンパイルされた関数本体を表現するコードオブジェクトです。 書き込み可能
__globals__
func_globals
関数のグローバル変数の入った辞書 (への参照) です ― この辞書は、関数が定義されているモジュールのグローバルな名前空間を決定します。 読み込み専用
__dict__
func_dict
任意の関数属性をサポートするための名前空間が収められています。 書き込み可能
__closure__
func_closure
None または関数の個々の自由変数 (引数以外の変数) に対して値を結び付けているセル (cell) 群からなるタプルになります。 読み込み専用

これらの特殊属性の使い方は以下を参照。

ところで、関数オブジェクトの属性func_codeとかfunc_closureって何?

func_code属性

func_codeは「コンパイルされた関数本体を表現するコードオブジェクトです。」とある。
func_code属性より得られるコードオブジェクトを使って、関数のソースコードに関する情報が得る事ができる。
コードオブジェクトについては以下を参照。

func_code属性とコードオブジェクトのco_varnames属性により、関数の引数名とローカル変数名を要素に持つタプルを取得する例を以下に示す。

def add_func(a, b):
    result = a + b
    return result

print add_func.func_code.co_varnames

実行結果

('a', 'b', 'result')

func_closure属性

func_closure属性には何が格納されているのだろうか?

「None または関数の個々の自由変数 (引数以外の変数) に対して値を結び付けているセル (cell) 群からなるタプルになります。」ってどういう意味?
ネットをググリまくって調べたところ...

上記の記事の内容を自分なりに解釈してまとめると、以下のような事になるのだろう、多分。

自由変数と束縛変数

クロージャ関数内で使われている変数には、自由変数と束縛変数という区別があるらしい。
以前にも「自由変数と束縛変数」という記事を書いたが、

関数の引数も含め、関数内で定義されている関数は束縛変数。
関数の外で定義されているが、関数内で利用されている関数は関数外から自由にアクセスできるという意味で自由変数になるらしい。

説明を単純化するために、デフォルトの引数を1つだけ持つ関数についてみていく事にする。
例えば、以下のような関数の場合は、

g="Free variable"
def func(arg):
    l="Bound variable"

gは自由変数でargとlは束縛変数という事になる。

では、以下のように関数内にネストした関数があった場合はどうかというと

g="g"
def func_a(arg_a="A"):
    a = "a"
    def func_b(arg_b="B"):
        b = "b"
        return g, a , b, arg_a, arg_b
    return func_b

上記の内側の関数を取得して、実行すると

func=func_a()
print func()

実行結果

('g', 'a', 'b', 'A', 'B')

外側の関数func_aにとっては、変数aや関数func_bそしてはfunc_aの引数であるarg_aは束縛変数であり、変数gは自由変数という事になる。
一方、内側のfunc_bにとってはどうかというと、func_b内で定義されている変数bとfunc_bの引数arg_bは束縛変数であり、func_bの外側で定義されているクローバル変数gやfunc_a内のローカル変数a,そしてfunc_aの引数arg_bも自由変数になってしまう。

内側のfunc_bにとって自由変数のgとaとarg_aの値をfunc_b関数の定義後に変更してやると

g="g"
def func_a(arg_a="A"):
    a = "a"
    def func_b(arg_b="B"):
        b = "b"
        return g, a , b, arg_a, arg_b
    a="changed a"
    arg_a="changed A"
    return func_b

func=func_a()
g="changed g"
print func()

実行結果

('changed g', 'changed a', 'b', 'changed A', 'B')

その結果は、内側の関数の自由変数に反映される。

なんで、自由変数と束縛変数を区別するかというと、自由変数は関数の外側で定義されている変数なので関数の外側において値が変更された場合に、その変更後の値を関数内で反映できなければならない。

このため、関数内の自由変数は通常のローカル変数(束縛変数)とは別の方法で変数を管理する必要が出てくる。

セルオブジェクト

python内部では、自由変数は関数の外側から変更されてもいいように、外部の変数を参照するセルオブジェクトという特別なオブジェクトを使って管理している。
セルオブジェクトに格納されている自由変数の値は、セルオブジェクトのcell_contents属性により取得する事ができる。

セルオブジェクトって何?

  • セルオブジェクト (cell object) — Python 2.7ja1 documentation

    “セル (cell)” オブジェクトは、複数のスコープから参照される変数群を実装するために使われます。セルは各変数について作成され、各々の値を記憶します; この値を参照する各スタックフレームにおけるローカル変数には、そのスタックフレームの外側で同じ値を参照しているセルに対する参照が入ります。セルで表現された値にアクセスすると、セルオブジェクト自体の代わりにセル内の値が使われます。このセルオブジェクトを使った間接参照 (dereference) は、インタプリタによって生成されたバイトコード内でサポートされている必要があります; セルオブジェクトにアクセスした際に、自動的に間接参照は起こりません。上記以外の状況では、セルオブジェクトは役に立たないはずです。

func_closureの正体

関数のfunc_closure属性は、関数内の自由変数のセルオブジェクトのタプルになっている。
内側の関数func_bを取得してfunc_closure属性を表示してみる。

func=func_a()
print func.func_closure

実行結果

(<cell at 0x000000000249DAF8: str object at 0x0000000001DA2BA0>, <cell at 0x000000000253F9D8: str object at 0x00000000024B0270>)

関数func_bのfunc_closure属性には、str型の値を持つ2個のセルオブジェクトが格納されていることになる。
そして、その値をcell_contents属性を使って表示してみると、

for cell_object in func.func_closure:
    print cell_object.cell_contents

実行結果

changed a
changed A

あれあれ?
関数func_aの自由変数はgとaとarg_aの3つでなかったの?
ひょっとして、グローバル変数は、また別の方法で管理されていて、セルオブジェクトに格納されないという事か。
pythonの実装上の問題なのか、グローバル変数はそもそも自由変数とは呼ばないのか?

参考のため外側の関数func_aのfunc_closure属性を表示させてみると。

実行結果

None

グローバル変数が自由変数でないとすると、外側の関数には自由変数が1個も存在せず、その場合はNoneを返すようだ。

セルオブジェクトの値は変更可能か?

cell_contents属性により自由変数の値は取得する事ができたが、値の設定も可能なのかやってみた。

func.func_closure[0].cell_contents="xxx"

実行結果

Traceback (most recent call last):
  File "ソースファイル名(フルパス)", line XX, in <module>
    func.func_closure[0].cell_contents="xxx"
AttributeError: attribute 'cell_contents' of 'cell' objects is not writable

「not writable」という事で変更はできない。

python3のnonlocal宣言を使ったらどうだろう。
python3ではfunc_closure属性が廃止されているようなのでかわりにここも__closure__属性に修正して

g="g"
def func_a(arg_a="A"):
    a = "a"
    def func_b(arg_b="B"):
        nonlocal a
        b = "b"
        return g, a , b, arg_a, arg_b
    return func_b

func=func_a()
print(func.__closure__[0].cell_contents)
func.__closure__[1].cell_contents="xxx"

実行結果

AttributeError: attribute 'cell_contents' of 'cell' objects is not writable

やっぱり値は変更はできないようだ。

少し複雑なパターン

以下のように、関数を更にネストした少し複雑な関数の場合はどうだろう。

def func_a(arg_a="A"):
    a="a"
    def func_b(arg_b="B"):
        b="b"
        def func_c(arg_c="C"):
            c="c"
            return g,a,b,c,arg_a,arg_b,arg_b,func_a,func_b,func_c
        return func_c
    return func_b
g="g"

関数func_bの中にが更にfunc_cがネストしている。
また、関数内の関数名も変数という見方ができるので、func_cの中でfunc_a,func_b,func_cも参照するようにreturn文に追加している。

func_closureの中身や関数の実行結果などを表示する関数を定義

def show_func_closure(func):
    print '-'*30, func.__name__,func()
    func_closure=func.func_closure
    print func_closure
    if func_closure:
        for cell_object in func_closure:
            print cell_object.cell_contents

上記の関数によりそれぞれの関数の状態を表示してみる。

func_b=func_a()
func_c=func_b()

show_func_closure(func_a)
show_func_closure(func_b)
show_func_closure(func_c)

実行結果

------------------------------ func_a <function func_b at 0x0000000002529748>
None
------------------------------ func_b <function func_c at 0x00000000025297B8>
(<cell at 0x000000000250F7F8: str object at 0x00000000023F61C0>, <cell at 0x000000000250F828: str object at 0x0000000002458AF8>, <cell at 0x000000000250F8B8: function object at 0x0000000002529668>)
a
A
<function func_b at 0x0000000002529668>
------------------------------ func_c ('g', 'a', 'b', 'c', 'A', 'B', 'B', <function func_a at 0x0000000001D8BF98>, <function func_b at 0x0000000002529668>, <function func_c at 0x00000000025296D8>)
(<cell at 0x000000000250F7F8: str object at 0x00000000023F61C0>, <cell at 0x000000000250F828: str object at 0x0000000002458AF8>, <cell at 0x000000000250F918: str object at 0x00000000023F6BE8>, <cell at 0x000000000250FEB8: str object at 0x0000000002406C38>, <cell at 0x000000000250F8B8: function object at 0x0000000002529668>, <cell at 0x000000000250FB28: function object at 0x00000000025296D8>)
a
A
B
b
<function func_b at 0x0000000002529668>
<function func_c at 0x00000000025296D8>

一番外側の関数func_aのfunc_closureの値は、やっぱりNoneになる。
func_bは自由変数として、aとarg_aそしてfunc_aで定義されている関数であるfunc_b自身を持つことになる。
同様に、func_cは自由変数として、aとarg_a,bとarg_b,func_bとfunc_c自身を持つことになる。
func_aはfunc_cにより参照されているが、グローバルな関数なのでやっぱり自由変数として扱われていない。

セルオブジェクトには自由変数が格納されているのはわかったが、func_closureが返すタプルの何番目のセルオブジェクトがどの自由変数を指すかを知る方法は無いようである。

デコレータはがし

デコレータ関数によりデコレートされた関数から、デコレートされる前のもとの関数を取り出す事をデコレータはがしと呼ぶようだ。

デコレータ関数は関数の引数にもとの関数を含んでいるので、デコレータ関数の内側にあるデコレートされる関数から見れば、もとの関数は自由変数という事にとなりセルオブジェクトに格納される。
このセルオブジェクトを取り出して、セルオブジェクトの値がfunction型であれば、もとの関数という事になり「デコレータはがし」ができる事になる。

実際にやってみよう。
以下のような、デコレータ関数とデコレートされる関数があったとする。

import functools

def dec_func(func):
    @functools.wraps(func)
    def wrapper_func(*list_args,**dict_args):
        u"ラッパー関数"
        print "before", func.__name__
        result=func(*list_args,**dict_args)
        print "after", func.__name__
        return result
    return wrapper_func

@dec_func
def func():
    u"デコレートされる関数"
    print u"もとの関数を実行"

この関数からもとの関数を取り出す。

import types

func_closure=func.func_closure
if func_closure:
    for cell_object in func_closure:
        cell_contents=cell_object.cell_contents
        if isinstance(cell_contents,types.FunctionType):
            org_func=cell_contents
            break

取り出した関数を実行してみる。

org_func()

実行結果

もとの関数を実行

inspectモジュールのgetsource関数を使ってデコレータはがしにより得られた関数org_funcのソースを表示してみよう。

import inspect
source_text = inspect.getsource(org_func.func_code)

実行結果

@dec_func
def func():
    u"デコレートされる関数"
    print u"もとの関数を実行"

「デコレータはがし」は成功したようだ。

しかし、これは今回のようなデコレータ関数の実装方法の場合について成功しただけであって、デコレータ関数の実装には他にもクラスを使った実装等があり、すべてのデコレータ関数に対応できるわけでは無い。

関数オブジェクトに任意の属性を設定

公式ドキュメントによると、関数オブジェクトは任意の属性を設定できるとある。

関数オブジェクトはまた、任意の属性を設定したり取得したりできます。この機能は、例えば関数にメタデータを付与したい場合などに使えます。関数の get や set には、通常のドット表記を使います。

関数もオブジェクトなので、任意の属性(関数オブジェクトのインスタンス変数)を自由に設定,取得できる。

def func():
    pass

func.new_attr="new"
print func.new_attr

実行結果

new

関数内で関数自身の属性を定義する事も可能。

def func():
    func.new_attr="new"

func()
print func.new_attr

間違いやすい点であるが、関数内で関数自身の属性を設定した場合には、関数が実行される前には関数内のコードはまだ実行されていないので、以下のように関数を実行する前に(属性を定義する前に)属性にアクセスするとエラーになってしまう。

def func():
    func.new_attr="new"

print func.new_attr

実行結果

Traceback (most recent call last):
  File "ソースファイル名(フルパス)", line x, in <module>
    print func.new_attr
AttributeError: 'function' object has no attribute 'new_attr'
ページのトップへ戻る