home changes contents help options

003:変数と定数

変数

Python には 変数名の先頭につけた記号によって、 変数がどの名前空間に所属しているか明示するような機能はありません。 (たとえばRubyでは $var の様に頭に$をつけるとグローバル変数になる)

print a や a + 1 のように変数が参照された際に、この a という変数が どの a を指すのかを決定する規則が存在します。

この規則の説明の前に、まず、ブロック、名前、束縛といった用語について簡単に説明します。

ブロック

ブロックは Python における実行単位で、モジュール、関数、クラス定義などのことです。

名前と束縛

名前はオブジェクトを参照するものです。名前は束縛操作を行うことでブロックに導入されます。束縛操作とは代入、 import 文の実行、クラスや関数の定義などです。

定義済みのある名前はあるブロック 1 つに属しています。

つづいて、参照された際、変数を探す規則について説明します。

名前の参照 (LEGB ルール)

名前が参照された際、変数を探す規則は LEGB ルールと呼ばれています。 LEGB ルールとは、まずローカルスコープを、そして見つからなければ順に外のスコープを見ていくというものです。

LEGB はそれぞれ、ローカルスコープ (Local scope) 、外側の関数のスコープ (Enclosing function's scope) 、グローバルスコープ (Global scope) 、ビルトインスコープ (Builtin scope) の頭文字です。

ローカルスコープ

ローカルスコープには、実行中のブロックが含まれます。ある名前を探す際、まず最初に調べられます。

def add1(x):
    a = x + 1 # 名前 a を add1 関数のブロック内で代入

    return a  # a は同一ブロック内で代入されているのが
              # 見つかるのでこちらが用いられる

a = 3 # 参照されない

add1 関数の外のモジュールブロックと add1 関数ブロック双方に a という名前が導入されていますが、 return a 文が実行された際に参照される a とは add1 関数内のものとなります。

外側の関数のスコープ(Enclosing function's scope)

関数ブロックがネストした際、外の関数ブロックらが含まれることとなるスコープです。関数ブロックがネストしていなければ存在しません。名前を探したがローカルスコープで見つからなかった時、このスコープが存在すれば、ここが調べられます。

def repeat(n):
    temp = n

    def _repeat():
        return temp # temp は _repeat 関数ブロックでは見つからないので
                    # 外の repeat 関数ブロックを探す。

  return _repeat

_repeat 関数が実行され temp という名前を参照したとき、この名前まず _repeat 関数ブロックで探されます。しかし見つからないので、次に外の repeat 関数ブロックで探されます。ここには temp という名前が導入されているのでこれが参照されることとなります。

なお、クラス文にてメソッドを定義するコードは、関数のネストに見た目が似ていますが異なるものです。

class Spam(object):
    a = 1

    def __init__(self):
        self.x = a # a を見つけることはできず、 NameError となる

__init__ メソッドが実行された際、 a の探される箇所は __init__ ブロックと次に紹介するグローバルスコープ、ビルトインスコープです。 Spam クラスのブロックが探されることはありません。

グローバルスコープ

実行中のブロックを含むモジュールブロックが含まれるスコープです。ローカル、外側の関数のスコープを探したが名前が見つからなかった時、このスコープが調べられます。

ONE = 1

def add1(x):
    return x + ONE # ONE は add1 関数ブロックでは見つからないので
                   # モジュールのブロックを探す。

グローバルスコープとローカルスコープが同一のものとなることがあります。実行中のブロックがモジュールブロックそのものであるときです。

x = 1
print x + 1 # この文の実行中、
            # x はローカルスコープの名前兼、グローバルスコープの名前

ビルトインスコープ

組み込み関数、組み込み定数、組み込み例外などが含まれる特殊なスコープです。名前を探す際、一番最後に調べられます。

def read_textfile(filename):
    try:
        f = open(filename, 'r') # open が導入された様子は
                                # このソースからは読み取れないが
                                # ビルトインスコープに確かに存在する
        text = f.read()
    finally:
        f.close()

    return text

グローバル変数へ代入する

代入などの名前の導入処理がとあるブロックでおこなわれると、その名前は通常ブロック自身に属することとなります。しかし、関数ブロックなどからモジュールのブロックへ代入を行いたい場合も、まれにあります。

次のコードは呼ばれた回数をグローバル変数 counter に保持しつつ、表示する関数 count を定義しようとしています。

counter = 0

def count():
    counter = counter + 1
    print count

この関数は正常動作しません。 Python インタプリタは func 内で count の代入が存在することを検知して、 count は func 関数ブロック内に存在するものと認識します。しかし count + 1 が実行される時点ではまだ count が関数ブロック内で束縛されていないため UnboundLocalError? 例外が送出されます。

この問題は counter が count 関数ブロックではなくモジュールブロックの counter を示したいことをインタプリタに伝えられれば解決します。そのための道具として global 文が存在します。次のようにコードを修正すると意図した動作となります。

counter = 0

def count():
    global counter
    counter = counter + 1
    print counter

外側の関数の変数へ代入する

ネストした関数ブロックの内側から外へ代入も通常できません。外のブロックのものを示したいことをインタプリタに伝えるため Python 3 では nunlocal 文が追加されました。

# Python 3 以降でのみ動作する
def count():
    counter = 0

    def _():
        nonlocal counter
        counter += 1
        return counter

    return _

c = count()
print(c()) # 1 を表示
print(c()) # 2 を表示
print(c()) # 3 を表示

属性参照

属性参照はピリオドを使って行います。

次の例は sys モジュールの version 属性にアクセスするものです。

import sys
print sys.version

インスタンスオブジェクトに対する属性参照はよく行われることですが、その名前の探される箇所は少々特殊です。インスタンスに名前が見つからなかった場合、クラス、そして親クラスとさかのぼって探索が続けられます。

class Spam(object):
    a = 0
    def __init__(self):
        self.b = 1

ins = Spam()

print ins.b # インスタンス属性 1 を表示する
print ins.a # クラス属性 0 を表示する

クラスが探索されるのはインスタンスに名前が見つからなかった時のみです。このためクラス属性と同名のインスタンス属性が存在すると、クラス属性のほうは隠蔽されることとなります。

class Ham(object):
    a = 0
    def __init__(self):
        self.a = 1

ins = Spam()

print ins.a # 1 を表示する

定数

Python には、代入し終えたらその対象を別のものに取り替えることができないようなもの、いわゆる定数は存在しません。

再代入して欲しくない変数はモジュールのグローバルスコープに大文字で定義する、という習慣はあります。 Python の推奨コーディング規約 PEP 8 には、大文字を用いる、単語の区切りはアンダースコアで、とあります。たとえば、 MAX_OVERFLOW, TOTAL などのような名前です。

この命名規則にしたがっている変数、変更されないことを意図した変数らを、便宜上、定数と呼ぶことはあります。

定数?

定数は作ることができませんが、再代入、削除ができない属性ならば作ることができます。

class ConstAttr(object):
    def __setattr__(self, name, value):
        if hasattr(self, name):
            raise AttributeError("Don't rebind %s" % name)
        object.__setattr__(self, name, value)

    def __delattr__(self, name):
        if hasattr(self, name):
            raise AttributeError("Don't unbind %s" % name)
        object.__delattr__(self, name)
>>> a = ConstAttr()
>>> a.x = 1
>>> a.x
1
>>> a.x = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "t.py", line 8, in __setattr__
    raise AttributeError("Don't rebind %s" % name)
AttributeError: Don't rebind x
>>> del a.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "t.py", line 13, in __delattr__
    raise AttributeError("Don't unbind %s" % name)
AttributeError: Don't unbind x

ただし、 __dict__ を特殊属性を直接操作したり、 object.__setattr__を用いれば、変更する事ができます。

>>> a.__dict__['x'] = 2
>>> a.x
2

キーワード

None は for や def, return などと同等のキーワードであり、特別扱いされています。

このため、束縛操作(代入など)を行うことはできません。 None = 0 のような代入文を書くと SyntaxError? となります。

ちなみに Python 3 では True, False もキーワードですが、 Python 2 ではそうではありません。

参考