関数定義 (その2)

スコープ

変数は定義される位置によって, スコープと呼ばれる有効範囲が異なります. 関数は独自のスコープを作り, 関数内部で定義される変数を関数の外側で使うことはできないようになっています.

次の振る舞いを確認してください.

In [1]: y = 'hello'

In [2]: z = 100

In [3]: def f(x):
   ...:     y = 2
   ...:     z = -1
   ...:     return x*2 + z
   ...: 

In [4]: f(1)
Out[4]: 1

In [5]: y
Out[5]: 'hello'

In [6]: z
Out[6]: 100
stackframe

関数の内部で定義されていた変数は, 関数の内部でのみ有効です. 一方, 関数の外側で定義された変数を使うことは可能です. ただし, 意図しない結果を招く恐れがあるので, このようなコードを書く必然性がなければ避ける方が懸命でしょう.

In [7]: g = 10

In [8]: def f(x):
   ...:     return g * x
   ...: 

In [9]: f(10)
Out[9]: 100

In [10]: g = 20  # 一見 f とは関係がないような変更

In [11]: f(10)   # しかし, f の挙動を変えてしまっている
Out[11]: 200

次のように書くほうが安全です.

In [12]: def f(x, g):
   ....:     return g * x
   ....: 

In [13]: f(10, g=10)
Out[13]: 100

In [14]: f(10, g=20)
Out[14]: 200

あるいは, 次のように書くのもよいかもしれません.

In [15]: def fg(g):
   ....:     def _f(x):
   ....:         return g * x
   ....:     return _f
   ....: 

In [16]: f = fg(10)

In [17]: f(10)
Out[17]: 100

In [18]: f = fg(20)  # 明示的な変更

In [19]: f(10)
Out[19]: 200

キーワード引数, デフォルト引数

キーワード引数

すでに上で例を出しましたが, 関数呼び出しの際に func_name(仮引数名=実引数) の形で 引数を渡すことができます. 次の関数を例にとって説明しましょう.

In [20]: def hello(name, title):
   ....:     print('Hello,', title, name, '!')
   ....: 

以下の呼び出し方法が認められています.

In [21]: hello('Brown', 'Ms.')  # 位置指定引数
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-1c05f23cc965> in <module>()
----> 1 hello('Brown', 'Ms.')  # 位置指定引数

<ipython-input-20-822ec1be3fb5> in hello(name, title)
      1 def hello(name, title):
----> 2     print('Hello,', title, name, '!')
      3 

TypeError: 'int' object is not callable

In [22]: hello(name='Brown', title='Mr.')  # キーワード引数
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-22-87c81f4b39d7> in <module>()
----> 1 hello(name='Brown', title='Mr.')  # キーワード引数

<ipython-input-20-822ec1be3fb5> in hello(name, title)
      1 def hello(name, title):
----> 2     print('Hello,', title, name, '!')
      3 

TypeError: 'int' object is not callable

In [23]: hello(title='Mr.', name='Brown')  # キーワード引数は順序交換可
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-23-42d5f7430632> in <module>()
----> 1 hello(title='Mr.', name='Brown')  # キーワード引数は順序交換可

<ipython-input-20-822ec1be3fb5> in hello(name, title)
      1 def hello(name, title):
----> 2     print('Hello,', title, name, '!')
      3 

TypeError: 'int' object is not callable

In [24]: hello('Brown', title='Mr.')  #  2個目だけキーワードというのも可
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-24-473e4ab22e3a> in <module>()
----> 1 hello('Brown', title='Mr.')  #  2個目だけキーワードというのも可

<ipython-input-20-822ec1be3fb5> in hello(name, title)
      1 def hello(name, title):
----> 2     print('Hello,', title, name, '!')
      3 

TypeError: 'int' object is not callable

しかし, 以下のような呼び出しは認められていません.

In [25]: hello('Mr.', name='Brown')  # 1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-f3293e11e39c> in <module>()
----> 1 hello('Mr.', name='Brown')  # 1

TypeError: hello() got multiple values for argument 'name'

In [26]: hello(title='Mr.', 'Brown')  #2
  File "<ipython-input-26-b52af31840cc>", line 1
    hello(title='Mr.', 'Brown')  #2
                      ^
SyntaxError: non-keyword arg after keyword arg
  1. 位置指定引数は前から順に評価される. 'President'name 変数に代入されているので, name キーワード引数があると2度代入しようとして TypeError になる.
  2. 位置指定引数より前にキーワード指定引数を書くことはできない. これは文法エラー (SyntaxError)

これらのエラーはエラーメッセージをきちんと読めば対応できるでしょう.

keyword-only argument

Python 3.x では, 次のように関数を定義することができます.

In [27]: def my_print(s, *, reverse):
   ....:     if reverse:
   ....:         print(s[::-1])
   ....:     else:
   ....:         print(s)
   ....: 

* よりあとに書かれた仮引数は, キーワード引数としてのみ渡すことができます (keyword-only argument).

In [28]: my_print('abc', reverse=True)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-28-7f51e34811fd> in <module>()
----> 1 my_print('abc', reverse=True)

<ipython-input-27-d94dc7c4b3cc> in my_print(s, reverse)
      1 def my_print(s, *, reverse):
      2     if reverse:
----> 3         print(s[::-1])
      4     else:
      5         print(s)

TypeError: 'int' object is not callable

In [29]: my_print('abc', reverse=False)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-29-0566970688b9> in <module>()
----> 1 my_print('abc', reverse=False)

<ipython-input-27-d94dc7c4b3cc> in my_print(s, reverse)
      3         print(s[::-1])
      4     else:
----> 5         print(s)
      6 

TypeError: 'int' object is not callable

これは, 次のような呼び出しを禁止するために使われます. (True が意味するものが何かが わかりにくいとコードの可読性が下がってしまうのです)

In [30]: my_print('error', True)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-30-dc202b040992> in <module>()
----> 1 my_print('error', True)

TypeError: my_print() takes 1 positional argument but 2 were given

デフォルト引数

関数にはデフォルト値を指定することができます.

In [31]: def salute(name, title, msg='Hello'):
   ....:     print(msg + ',', title, name + '!')
   ....: 

In [32]: salute('Brown', 'Mr.')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-32-62c89680e02a> in <module>()
----> 1 salute('Brown', 'Mr.')

<ipython-input-31-8ab58816c14f> in salute(name, title, msg)
      1 def salute(name, title, msg='Hello'):
----> 2     print(msg + ',', title, name + '!')
      3 

TypeError: 'int' object is not callable

In [33]: salute('Brown', 'Mr.', 'Good morning')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-33-e4f8e8313238> in <module>()
----> 1 salute('Brown', 'Mr.', 'Good morning')

<ipython-input-31-8ab58816c14f> in salute(name, title, msg)
      1 def salute(name, title, msg='Hello'):
----> 2     print(msg + ',', title, name + '!')
      3 

TypeError: 'int' object is not callable

デフォルト値として None がよく使われます. 特定の引数が設定されているときと そうでないときで関数の振る舞いを変えたい場合に使います.