初めてのPythonを読んでみる(8)

  • 13章 スコープと引数

スコープには以下の3つがある。

  • グローバルスコープ ... モジュールのトップレベ
  • ローカルスコープ ... 関数の呼び出し単位
  • ビルトインスコープ ... 言語組み込みのスコープ。 __builtin__ で定義されている変数が所属
>>> x = 1
>>> def func():
...     x = 10
...     def innerfunc():
...         x = 100
...         print  x
...     innerfunc()
...     print x
...
>>> func()
100
10

変数に代入が行われた時点で、ローカルスコープに属する変数となる。※オブジェクトの上書きと代入は別。

>>> L = []
>>> def func():
...     L.append(1)
...     L.append(2)
...
>>> L
[]
>>> func()
>>> L
[1, 2]
>>> L = []
>>> def func():
...     X = [1,2,3]
...     L = X
...
>>> L
[]
>>> func()
>>> L
[]
>>> L = []
>>> def func():
...     L[:] = [1,2]
...
>>> L
[]
>>> func()
>>> L
[1, 2]

LEGBルール (local, enclosing, global, builtin) と呼ばれるものがある。Pythonのスコープに関するルール。
enclosing スコープの例は以下。innerfunc() から、外側の変数 x を参照している

>>> def func():
...     x = 10
...     def innerfunc():
...             print x
...     innerfunc()
...
>>>
>>> func()
10

オブジェクトの属性名に関するルールは、上記とは別である。


タプルがあるおかげで、複数の値を戻す関数を作るのが簡単。実際にはタプルを戻していて、代入時にアンパックが行われているというからくり。

>>> def swap(x, y):
...     return y, x
...
>>> swap(1, 2)
(2, 1)
>>> a, b = swap(1, 2)
>>> a
2
>>> b
1

引数のバリエーションとして「キーワード引数」「可変長引数」「引数デフォルト値指定」がある。

# キーワード引数の例
>>> def func(a, b, c): print a, b, c
...
>>> func(1, 2, 3)
1 2 3
>>> func(c=1, b=3, a=2)
2 3 1

# 引数デフォルト値指定
>>> def func(a, b=0, c=0): print a, b, c
...
>>> func(1)
1 0 0
>>> func(1, 2)
1 2 0

# 可変長引数(1)
# *args に渡した可変個の引数はタプル化される
>>> def times(*args):
...     return reduce(lambda x,y:x*y, args)
...
>>> times(1,2,3,4,5,6)
720

# 可変長引数(2)
# **args に渡した可変個の引数はディクショナリ化される
>>> def foo(**args):
...     print args
...
>>> foo(1,2,3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 0 arguments (3 given)
>>>
>>> foo(a=1, b=2)
{'a': 1, 'b': 2}
>>>
>>> foo(a=1, b=2, c=2)
{'a': 1, 'c': 2, 'b': 2}
>>>
>>> foo(a=1, b=2, c=2, a=3)  # 同じキーワードがあると後のほうに出てくる値で上書きされるっぽい
{'a': 3, 'c': 2, 'b': 2}
>>>
>>> foo(a=1, b=2, c=2, a=3, c=1, c=0)
{'a': 3, 'c': 0, 'b': 2}

Pythonでは、関数は一般に「常に正しい引数が指定される」という前提で作った方が得策。引数が正しくなければ自動的に例外が発生するので、それに任せるが良し。


引数指定に関するNGの例。

# キーワード引数の後に通常の引数を指定してはダメ。
>>> func(a=1, 2)
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg

# 可変長引数の後に通常の引数は書けない。
>>> def func(*args, b):
  File "<stdin>", line 1
    def func(*args, b):
                    ^
SyntaxError: invalid syntax

# *引数は同時に2つは書けない。
>>> def func(*a, *b):
  File "<stdin>", line 1
    def func(*a, *b):
                 ^
SyntaxError: invalid syntax

# * と ** の併用はOK。
>>> def func(*a, **b):
...     print a, b
...

# ただし順序は *, ** の順で。
>>> def func(**a, *b):
  File "<stdin>", line 1
    def func(**a, *b):
                ^
SyntaxError: invalid syntax
  • 14章 関数に関連する高度なテクニック
  • lambda 式

lambda は無名関数とか呼ばれる。名前がないので、何かに代入して使う。インラインでの関数定義(コールバック関数とか)に良く使われる。書式は以下の通り:

lambda arg1, arg2, ..., argN : 引数を使用する式

lambda は「式」である。これ重要。def はステートメントなので書ける場所が制限されるが、lambda は式なので、書ける場所が多い。

lambda の例:

# 前出の関数。
>>> def times(*args):
...     return reduce(lambda x,y: x*y, args)
...
>>> times(1,2,3,4,5,6)
720

# 上記を lambda で書いてみる。
>>> fn = lambda *args: reduce(lambda x,y: x*y, args)
>>> fn(1,2,3,4,5,6)
720

# 一応、可変長引数も取れるみたい。キーワード引数や引数デフォルト値も普通に使えそうだな。
>>> fn = lambda a, b=0, c=0: a+b+c
>>> fn(1)
1
>>> fn(a=10, b=5)
15

# lambda は式なので、いろんなところで書ける。
>>> L = [(lambda x: x), (lambda x: x**2), (lambda x: x**3), (lambda x: x**4)]
>>>
>>> for fn in L:
...     print fn(3),
...
3 9 27 81

# lambda 内から、外側の変数を覗くことが可能
>>> def func(x):
...     return (lambda y: x+y)
...
>>> fn = func(10)
>>> fn
<function <lambda> at 0x00AF5CB0>
>>> fn(20)
30

# lambda のネストでも同様に、内側から外側の変数が見える
>>> fn = lambda x: (lambda y: x*y)
>>> fn
<function <lambda> at 0x00AF5DB0>
>>> fn(10)
<function <lambda> at 0x00AF5CB0>
>>> fn(10)(10)
100


14章はこのあとにもリスト内包、ジェネレータなどてんこ盛りな内容なので、とりあえずここまでで一旦ストップ。