2014年12月16日火曜日

locals と globals

Python は、辞書を用いてローカル変数やグローバル変数へアクセスするための 二つのビルトイン関数、locals と globals を持っている。

locals を覚えてるだろうか? はじめて見たのは以下のコードにおいてあろう:

def unknown_starttag(self, tag, attrs):

strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])

self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

  いや、待ってほしい、読者はまだ locals について学習することはできない。 まず最初に、名前空間について、学ぶ必要があるのだ。 これは無味乾燥なものかもしれないが、重要なので注意を払ってほしい。

  Python は、変数を追跡し続けるために、名前空間と呼ばれるものを用いている。 名前空間は、単に辞書のようなものであり、そのキーは「変数名」、値は「変数の値」である。 実際、すぐ確認できるのだが、Python の辞書と同じように名前空間にアクセスすることができる。

  Python プログラムのいかなるポイントにおいても、いくつかの名前空間の変数が存在する。 それぞれの関数は それ自身の名前空間を持っており、それが(引数やローカル変数といった)関数の変数を追跡し続ける。 それぞれのモジュールは、「グローバルな名前空間」と呼ばれる自分自身の名前空間をもっており、それによって、(関数、クラス、他のすべでのインポートされたモジュール、モジュール・レベルの変数、定数といった)モジュール変数を追跡し続ける。 そして、ビルトイン関数や例外を保持する「ビルトイン名前空間」が存在する。

  コードのある行が変数 x の値を問い合わせたとき、Python は その時点でアクセス可能なすべての名前空間の中を順番に、その変数を検索する:

1. ローカルな名前空間 --- 現在の関数やクラス・メソッドに特有な名前空間のこと。 もしも関数がローカル変数 x を定義したり、引数 x を持っていると、Python はこれを使い、検索をストップする。

2. グローバルな名前空間 --- 現在のモジュールに特有な名前空間のこと。 もしもあるモジュールがx という変数、 xという関数、あるいは、x というクラスを定義していた場合、Python はこれを用い、検索をストップする。

3. ビルトイン名前空間 --- すべてのモジュールに対してグローバルな名前空間のこと。 最後の手段として、Python は、x がビルトイン関数か変数ではないかと推測する。

  もしも Python が x をいかなる名前空間においても発見出来なかった場合、ギブアップし、There is no variable named 'x' というメッセージとともに NameError を返す。 この挙動は、例3.18で見たとおりである。 読者は、解放された変数を参照する際、Python が このエラーを返す前にどれだけ多くの仕事をこなしたかについて、感謝してはこなかったであろう。


 Python 2.2 は、名前空間の検索に影響するような、微妙ではあるが重要な変更を行った: それは、ネストされたスコープである。 Python 2.2 以前のバージョンでは、ネストされた関数やlambda 関数の中の変数を参照する際、Python はまず現在の(ネストされた、あるいは、lambda)関数の名前空間を検索し、次に、親の関数の名前空間を検索し、それから、モジュールの名前空間を検索していた。 Python 2.1 は、どちらの方法でも動作する; デフォルトでは、Python 2.0 のように動作するが、コードの先頭に次の行を付け加えることにより、そのモジュールを、Python 2.2 のように動作させることも可能である:
from __future__ import nested_scopes

  まだ混乱しているだろうか? おちこむことはない! 約束するが、これは非常にクールなことである。 Python における、他の多くの事柄と同じく、名前空間は、プログラムの実行時に直接アクセス可能である。 しかし、どうやって? ローカルの名前空間へは、ビルトインされた locals 関数によって、アクセスできる。 そして、(モジュラー・レベルでの)グローバルな名前空間には、ビルトインされた globals 関数によって、アクセスできる。

Example 8.10. locals の導入

>>> def foo(arg):

... x = 1

... print locals()

...

>>> foo(7)

{'arg': 7, 'x': 1}

>>> foo('bar')

{'arg': 'bar', 'x': 1}

 関数 foo は、ローカルな名前空間に二つの変数を持っている; ひとつは arg であり、その値は関数に引き渡される。 そしてもうひとつは x であり、関数の中で定義されている。

locals は 「名前/値」ペアに関する辞書を返す。この辞書のキーは、変数名(文字列型)である。したがって、foo を 7 という引数で呼び出すと、この関数の二つのローカル変数(arg(7) と  x(1) )を含む辞書を表示する。

Python は動的なタイピングをサポートしており、arg に対して簡単に文字列を渡せることを思い出してほしい; この関数(そして、locals の呼び出し)はうまく動作する。locals は、あらゆるデータ型の変数を取り扱える。

  from module import と import module の違いを覚えているだろうか? import module では、モジュールそのものがインポートされていたが、それ自身の名前空間は保持されていた。したがって、そのモジュールが含むいかなる関数やアトリビュートに対しても、アクセスの際にはモジュール名が必要であった: module.function。しかし、from module import を用いると、本当に、特定の関数とそのアトリビュートを他のモジュールからインポートし、あなた自身がもっている名前空間に付け加えることになる。もともとのモジュールを参照することなく、関数名のみで直接アクセスができるのは、これが理由である。globals 関数を用いると、ここで起きていることを実際に眺めることができる。

Example 8.11. globals の導入

BaseHTMLProcessor.py の最下部にある、以下のコード・ブロックを見てほしい:

if __name__ == "__main__":

for k, v in globals().items():

print k, "=", v

 おじけづくことはない、以前、このコードを読んだことを思い出そう。 globals 関数は辞書を返し、あなたは、items メソッドとマルチ・バリアブル・アサイメントを用いて、この辞書の中身について繰り返し反復を行った。 ここで新たに導入されたのは、globals 関数のみである。



ここで、スクリプトをコマンドラインから実行すると、以下のアウトプットが得られる。 (ただし、読者がPython をインストールした環境に応じて、このアウトプットは若干異なるかもしれないことに、注意してほしい。)

c:\docbook\dip\py> python BaseHTMLProcessor.py

SGMLParser = sgmllib.SGMLParser

htmlentitydefs = <module 'htmlentitydefs' from 'C:\Python23\lib\htmlentitydefs.py'>

BaseHTMLProcessor = __main__.BaseHTMLProcessor

__name__ = __main__

... rest of output omitted for brevity...

from module import によって、SGMLParser は sgmlib からインポートされた。これは、モジュールの名前空間へ直接とりこまれたことを意味し、実際、そうなっている。

 これを、import によってインポートされた htmlentitiydefs と比べている。htmlentitydefsモジュール自身は名前空間の中にあるが、htmlentitydefs の中で定義された変数 entitydefs はそうでは無い。

 このモジュールは、BaseHTMLProcessor という一つのクラスを定義しているのみであり、そうなっている。ここでの値はクラスそのものであり、クラスの特定のインスタンスでは無いことに注意せよ。

if __name__ トリックについて覚えているだろうか? モジュールを実行する際、(他のモジュールからのインポートに反して)、ビルトインの __name__ アトリビュートは 特殊な値 __main__ になる。 このモジュールは、コマンドラインからスクリプトとして実行されているので、__name__ は __main__ であり、これが、globals を表示する小さなコードが実行された理由である。




locals 関数と globals 関数を使うと、変数名を文字列として与えることにより、任意の変数の値を動的に取得することができる。 これは、関数名を文字列として与えることにより、任意の関数へアクセスすることを可能とする、getattr 関数の機能をうつしたものである。

locals と globals 関数にはもうひとつ重要な違いがあり、それが あなたに噛み付く前に学んでほしい。 それは、とにかく いつか  あなたに噛み付くだろうが、すくなくともその時、既にそれを学んだことを思い出してほしい。

Example 8.12. locals はリード・オンリーであり、 globals はそうではない

def foo(arg):

x = 1

print locals()

locals()["x"] = 2

print "x=",x



z = 7

print "z=",z

foo(3)

globals()["z"] = 8

print "z=",z

foo は、引数 3 とともに呼び出されたので、これは {'arg': 3, 'x': 1} を表示するであろう。これは、何ら驚くべきことではない。

locals は辞書を返す関数であり、ここではその辞書の値を設定している。これにより、ローカル変数 x の値が 2 に変わると思うかもしれないが、しかし、そうではない。locals は、実際にローカルな名前空間を返しているわけではなく、そのコピーを返しているのである。したがって、それを変更してもローカルな名前空間の変数の値は変わらないのである。

x=2 ではなく、x=1 が表示される。

locals によってやけどした後なので、こうやっても z の値は変わらないと思うかもしれない。しかし、今度は変わるのである。Python の内部仕様の相違(これについては深追いしない。なぜなら、筆者も完全に理解しているわけでは無いので)により、globals は、名前空間のコピーではなく、名前空間そのもを返す: これは、locals とは正反対の挙動である。従って、globals が返した辞書へのあらゆる変更は、ただちにグローバル変数に影響を及ぼす。

z = 7 ではなく z = 8 が表示される。

0 件のコメント:

コメントを投稿