初めてのPython(16章)

変数のスコープと関数の引数について

変数のスコープ

Pythonの変数はすべていずれかの名前空間に属する、モジュール全体であったり、関数であったり。
プログラムで変数を利用すると以下のどちらかが行われる。

上記2つの処理を行うにあたって、変数の有効範囲(スコープ)が重要になる。
変数のスコープは、『どの名前空間に属するか』で決まり、
変数が属する名前空間は、『プログラムのどこで最初に代入されたか』で決まる。
代入処理をしないで使われた変数は、自分の一つ外に属する名前空間の変数が利用される。
つまり、既存変数が参照/更新されることになる(mutableオブジェクトの変更は要注意)。

ここでの代入は=演算子による代入だけではなく、次も代入として扱われる

  • モジュールのインポート処理による別モジュールの属性インポート
  • 関数呼び出し時の引数渡し

異なるスコープで代入された変数は、名前空間に存在しないため新しい変数として名前空間に作成される。

  • 関数外で代入した変数と同名の変数を関数内で変更したとしても、関数外の変数は影響を受けない
  • そもそも、それぞれの変数は全く別のものとして管理される

スコープの種類は基本的に3種類

  1. ビルドインスコープ
  2. グローバルスコープ
  3. ローカルスコープ
ビルドインスコープ

Pythonにあらかじめ用意されている変数のスコープで、プログラム中のどこでも有効。

グローバルスコープ

モジュールファイルのトップレベルで最初に代入された変数のスコープ。
モジュールファイル中はどこでも有効で、グローバルスコープを有する変数をグローバル変数と呼ぶ。
globalステートメントを使って代入処理を行うと、グローバルスコープの変数となる。
グローバルスコープで定義済みであれば変更、未定義であれば新規に作成される。
グローバル変数のアクセス方法がP343に色々と書いてあって面白かった。

ローカルスコープ

関数内で最初に代入された変数のスコープ。
注意すべきは、スコープが作成されるのは”関数が呼び出された時”で、定義された時ではない。
これは、再帰呼び出し実施時に変数のスコープを正しく管理するための実装。

LEGBルール

変数の検索順のルールのことで、それぞれスコープの頭文字をとったもの。
LはLocal, GはGlobal, BはBuiltin。
EはEnclosing function's scopeのことで、Localスコープの一種でネストスコープと呼ばれる。
関数がネストして定義されている時に作成されるもので、
外側の関数に属するものがE、内側の関数に属するものがLとなる。

ネストスコープ

ネストスコープが存在するときのglobalステートメントの意味が代入と参照で異なる
(代入処理)
内側の関数でglobalをつけて代入すると、ローカルスコープじゃなく、ネストスコープの変数を利用する。
単に代入すると、ローカルスコープの変数を利用する。

(参照処理)
参照の仕方は、内側から外側へ検索することは変わらないが、
globalをつけて参照すると『ネストスコープ』ではなく『グローバルスコープ』の変数から検索を始める。

以下のことも触れられているが、難しくてよくわかんない。

  • ファクトリ関数(P.345)
  • ネストスコープの状態をネストされた関数の引数のデフォルト値にする(P.347〜P.349)

関数の引数

関数の引数は、
呼び出し側で指定したオブジェクトが、定義側で指定した変数に代入される形で行われる。
オブジェクトと変数のマッチングの基本は、指定した位置でのマッチング。
それ以外にも変数の名前を使う名前によるマッチングもある。
そして、引数にデフォルト値を持たせたり、引数の数を自由に変えられるようにすることも可能。

呼び出し/定義側で使用可能な変数の指定方法は以下。

書き方 使う場所 意味
func(value) 呼び出し側 位置でマッチングする方式で関数を呼び出す
func(name=value) 呼び出し側 名前でマッチングする方式で関数を呼び出す
func(*name) 呼び出し側 シーケンスを渡すことで、引数の数を可変にして関数を呼び出す
func(**name) 呼び出し側 ディクショナリを渡すことで、変数の数を可変にして関数を呼び出す
def func(name) 定義側 位置でマッチングする方式で関数を定義する
def func(name=value) 定義側 呼び出し側で、指定されなかった時に使うデフォルト値を指定して関数を定義する
def func(*name) 定義側 func(*name)に対応する定義方法、渡されたオブジェクトはタプルに納められる
def func(**name) 定義側 func(**name)に対応する定義方法、渡されたオブジェクトはディクショナリに納められる

「デフォルト値」を使うことで、引数が「少ない」場合でも呼び出せる。
「*/**」を使うことで、引数が「多い」場合でも呼び出せる。

キーワード引数

名前でマッチングするから、呼び出し位置は関係なくなる。
キーワードを省略すると位置によるマッチングがされる。
キーワード指定したものは除いて、位置でマッチングされたら幸せだが、そうは問屋がおろしてくれなかった。
前から順に位置で指定されて、位置のマッチングが終わって足りないものが名前でマッチングされる。

>>> def func(a, b, c):
...     print a, b, c
... 
>>> func(c=1, b=2, a=3)
3 2 1
>>> func(3, c=1, b=2)
3 2 1
>>> func(3, 1, b=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() got multiple values for keyword argument 'b'
>>> 
デフォルト値

あらかじめデフォルト値が指定されているから、呼び出し元で変数を渡さなくても良くなる。
キーワード引数と組み合わせて指定すると、変数の指定順と指定個数を無視して関数を呼び出すことが出来る。
それでもやっぱり、位置でマッチングする変数は先においとかないとだめ。

>>> def func(a, b=2, c=3):
...     print a, b, c
... 
>>> func(10)
10 2 3
>>> func(1, c=5)
1 2 5
>>> func(c=5, 1)
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> 
複数の引数を受け取る

"*"を使って定義することで、位置でマッチングできなかったものが全てタプルに納められる。
"**"の場合は、キーワード引数のみに対応でき、ディクショナリに納められる。

>>> def func(a, *args):
...     print a, args
... 
>>> func(1,2,3,4)
1 (2, 3, 4)
>>> func(1, [2,3,4])
1 ([2, 3, 4],)
>>> func(1, (2,3,4))
1 ((2, 3, 4),)
>>> func(1, "234")
1 ('234',)
>>> func(1, {"a":1, "b":2})
1 ({'a': 1, 'b': 2},)
>>> 
>>> func(1, b=2, c=3, d=4)
1 {'c': 3, 'b': 2, 'd': 4}
>>> func(1, {"b":2, "c":3, "d":4})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 1 argument (2 given)
>>> 
複数の引数を渡す

シーケンスを渡す場合は"*"を使って、ディクショナリを渡す場合は"**"を使う。
シーケンスは位置でマッチングが行われるが、ディクショナリはキーを使った名前によるマッチングが行われる。

>>> def func(a, b, c, d):
...     print a, b, c, d
... 
>>> func(*[1,2,3,4])
1 2 3 4
>>> list=[1,2,3,4]
>>> func(*list)
1 2 3 4
>>> func(*(1,2,3,4))
1 2 3 4
>>> tupple=(1,2,3,4)
>>> func(*tupple)
1 2 3 4
>>> func(*"1234")
1 2 3 4
>>> str="1234"
>>> func(*str)
1 2 3 4
>>> 
>>> dict={'a':1, 'b':2, 'c':3, 'd':4}
>>> func(**dict)
1 2 3 4
>>> func(**{'a':1, 'b':2, 'c':3, 'd':4})
1 2 3 4
>>> 
>>> func(1,2, **{'d':4, 'c':3})
1 2 3 4
>>> func(1, c=3, *[2], **{'d':4})
1 2 3 4
>>>