Python/関数
初歩的な内容
[編集]関数
[編集]関数は、プログラミングにおいて再利用可能なコードのブロックです。特定のタスクや処理を実行するために設計されています。関数は、特定の入力(引数)を受け取り、それを処理し、結果を出力(戻り値)として返すことができます。
関数の主な利点は次の通りです:
- 再利用性: 同じ処理を繰り返し記述する必要がなく、関数を呼び出すことで同じ処理を実行できます。
- モジュール化: コードをより小さな部分に分割し、管理しやすくします。
- 保守性: コードを関数単位で修正したり変更したりすることが容易になります。
- 可読性: 適切に名前付けされた関数は、コードを理解しやすくします。
プログラミング言語によって関数の使い方や書き方は異なりますが、基本的な構造は似ています。関数は通常、次のような形式で定義されます:
def function_name(parameters): # 処理 # ... return result
例えば、Pythonでの関数定義はこのようになります。
def add_numbers(a, b): sum = a + b return sum
この例では、add_numbers
という関数が定義されており、a
とb
という2つの引数を受け取ります。これらの引数を使ってsum
を計算し、その結果をreturn
文で返します。
関数はその他にも多くの機能を持ち、異なる言語においても様々な特性を持っています。
引数のある関数
[編集]- (コード例)
def func(a) : print("計算するよ") b = a * 2 print(a,"を2倍したら",b) a = 1 func(a) a = 5 func(a) a = 80 func(a)
(実行結果↓)
計算するよ 1 を2倍したら 2 計算するよ 5 を2倍したら 10 計算するよ 80 を2倍したら 160
ある処理を繰返し行いたい場合、関数をつかうことで、記述をへらせます。 関数に作業させる際、関数外で定義された値を、上記のように利用する事ができ、そのような変数を「引数」(ひきすう)といいます。
上記のコードのうち、関数の定義部分は、
def func(a) : print("計算するよ") b = a * 2 print(a,"を2倍したら",b)
↑ この範囲が、関数の定義部分です。
引数のある関数の定義の書式は、
def 関数名(引数) : 処理内容
というふうになります。
「引数」は「ひきすう」と読みます。
引数が2個以上の場合
[編集]def menseki(a,b) : print("面積を計算するよ") print(a * b) a = 3 b = 5 menseki(a,b)
- 結果
面積を計算するよ 15
下記のように、呼び出し時に引数の値を「menseki(3,5)」のように指定することもできます。
def menseki(a,b) : print("面積を計算するよ") print(a * b) menseki(3,5)
- 結果
面積を計算するよ 15
やや難しい内容
[編集]グローバル変数とローカル変数
[編集]""" グローバル変数とローカル変数の例 """ a = 1 b = 2 c = 3 def f2(): """ f2で参照されるa,b,cはグローバル変数 """ print(f"f2:{a=},{b=},{c=},") def func(a) : """ funcで参照されるaは仮引数 funcで参照されるbはローカル変数 funcで参照されるcはグローバル変数 """ b = a * 2 print(f"func:{a=},{b=},{c=},") f2() func(111) print(f"GLOBAL:{a=},{b=},{c=},")
- 実行結果
func:a=111,b=222,c=3, f2:a=1,b=2,c=3, GLOBAL:a=1,b=2,c=3,
- f2
- 関数から呼び出された関数で、仮引数にもローカル変数にも見つからなかった変数は、グローバル変数から探される。
- func
- 仮引数とグローバル変数の名前が同じ場合、仮引数が参照される。
- グローバル変数は仮引数にシャドーイングされる。
- ローカル変数とグローバル変数の名前が同じ場合、ローカル変数が参照される。
- グローバル変数はローカル変数にシャドーイングされる。
- ローカル変数の有効範囲(スコープ)は、関数の終わりまでです。
このようにpythonでは、関数内で数を置きかえる動作は、原則として、関数外の動作に影響を与えません。
組込み関数id
[編集]組込み関数idを使うとオブジェクトのidを得ることができます。 2つの変数同士のidが同じ場合、2つの変数は同じオブジェクトにバインドされています。 大概の型のオブジェクトのidはメモリー上のアドレスですが、整数のようにそうではない型も少数ながらあります。
- idの例
""" idの例 """ a = 1 b = 2 c = 3 def f2(): """ f2で参照されるa,b,cはグローバル変数 """ print(f"f2:{a=}({id(a)=}),{b=}({id(b)=}),{c=}({id(c)=}),") def func(a) : """ funcで参照されるaは仮引数 funcで参照されるbはローカル変数 funcで参照されるcはグローバル変数 """ b = a * 2 print(f"func:{a=}({id(a)=}),{b=}({id(b)=}),{c=}({id(c)=}),") f2() func(111) print(f"GLOBAL:{a=}({id(a)=}),{b=}({id(b)=}),{c=}({id(c)=}),")
- 実行結果
func:a=111(id(a)=9792128),b=222(id(b)=9795680),c=3(id(c)=9788672), f2:a=1(id(a)=9788608),b=2(id(b)=9788640),c=3(id(c)=9788672), GLOBAL:a=1(id(a)=9788608),b=2(id(b)=9788640),c=3(id(c)=9788672),
戻り値
[編集]関数の戻り値(return value;返り値・返却値とも)は、return 文で返します。
- 関数の戻り値
import inspect def f(a) : """ 引数を戻り値とする関数 """ return a print(inspect.getsource(f)) print(f'''\ {f(1)=} {f(3.1415926536)=} {f("string")=} ''') def n(a) : """ return文のない関数 """ pass print(inspect.getsource(n)) print(f'''\ {n(n)=} {n(3.1415926536)=} {n("string")=} ''') def x(a) : """ return文に引数のない関数 """ return print(inspect.getsource(x)) print(f'''\ {x(n)=} {x(3.1415926536)=} {x("string")=} ''')
- 関数は多くのケースで何らかの値を返します。
- これを戻り値とよびます。
- 関数は、return文の引数を戻り値とします。
- return文に出会わず関数定義の終わりに達した場合は、オブジェクトNoneが返ります。
- return文で戻り値を指定しない場合は、オブジェクトNoneが返ります。
- 例
def f(a, b) : return a + b
- この関数は引数 a と b を受けとり、足した結果を返します。
return文による途中終了
[編集]return文があると、その行で関数が終了します。
- 例
def work() : print("a") return 0 print("b") work() work()
- 実行結果
a a
- print("b") が実行されることはありません。
キーワード引数
[編集]関数の仮引数には名前(識別子)がありますが、仮引数を明示して関数の実引数を渡し呼出すことができます。 このような識別子を明示して呼びされた実引数をキーワード引数と言います。
- キーワード引数
def sub(left, right) : return left - right print(f"{sub(3, 4)=}") print(f"{sub(right=8, left=19)=}")
- 実行結果
sub(3, 4)=-1 sub(right=8, left=19)=11
- 同じ関数を、キーワード引数で呼出すことも、従前の引数形式で呼出すこともできます。
- キーワード引数では引数の順序は問わず、仮引数の識別子で参照されます。
- 上記の例では第一引数と第二引数の順序が入れ替わっています。
- #可変長引数関数を使うことで、キーワード引数で呼出されたのか、従前の引数形式で呼出されたかを判別できます。
ディフォルト引数
[編集]関数の引数には、既定値(ディフォルト)を設けることができます。
- 例
def add(a=0, b=0, c=0) : return a+b+c print(f"""\ {add()=} {add(1)=} {add(2,3)=} {add(4,5,6)=} """)
- 実行結果
add()=0 add(1)=1 add(2,3)=5 add(4,5,6)=15
可変長引数関数
[編集]関数の引数は可変長にできます。
- 例
def add(*args, **kwargs) : result = 0 for i in args : result += i for _, i in kwargs.items() : result += i return result print(f"""\ {add()=} {add(1)=} {add(2,3)=} {add(4,5,6)=} {add(1,2,3,4,5,6,7,8,9,10)=} {add(a=1)=} {add(a=2,x=3)=} {add(q=4,p=5,z=6)=} """)
- 実行結果
add()=0 add(1)=1 add(2,3)=5 add(4,5,6)=15 add(1,2,3,4,5,6,7,8,9,10)=55 add(a=1)=1 add(a=2,x=3)=5 add(q=4,p=5,z=6)=15
- この様に、キーワード引数を可変長引数に含めることもできます。
残余引数
[編集]関数の定義で固定された引数とともに、可変長引数を持つことができます。
ここで、残余引数は、可変長引数の後ろに定義される一連の引数です。
残余引数のために、アスタリスク(*
)を使用します。
def my_func(a, b, *args): print(a) print(b) for arg in args: print(arg)
これで、my_func()関数は少なくともaとbの2つの引数を取り、残りの引数を可変長引数として扱います。
多値返却風な処理
[編集]Pythonには、厳密な意味での多値返却はありませんが、タプルやリストを返すことで多値返しに近いことができます。
- 多値返し風な処理
def addsub(x, y) : a, b = x + y, x - y return a, b def mulquoremdiv(x, y) : return x * y, x // y, x % y, x / y a, s = addsub(13, 5) m, q, r, d = mulquoremdiv(19, 7) print(f'''\ {a=}, {s=} {m=}, {q=}, {r=}, {d=} ''')
- 実行結果
a=18, s=8 m=133, q=2, r=5, d=2.7142857142857144
「多値返却風な処理」や「多値返しに近いこと」と歯切れが悪いのは、型アノテーションを
def addsub(x: int, y: int) -> int, int: # Error! a, b = x + y, x - y return a, b def mulquoremdiv(x: int, y: int) -> int, int, int, float: # Error! return x * y, x // y, x % y, x / y
としたいのですが、
from typing import Tuple def addsub(x: int, y: int) -> Tuple[int, int]: a, b = x + y, x - y return a, b def mulquoremdiv(x: int, y: int) -> Tuple[int, int, int, float] : return x * y, x // y, x % y, x / y
としなければならず、タプルを返していることを明確に意識する必要があるからです。
デコレーター
[編集]デコレーター(decorator)は、関数の内容を書き換えずに修飾するための仕組みです。 関数の引数に関数を指定します。既存の関数の前に@{デコレーター名}を付けることで、その関数を修飾することができます。
- 形式
def デコレーター名(デコレーターの引数) : def ラッパー関数名(*args, **kwargs) : # 前処理 result = デコレーターの引数(*args, **kwargs) # 後処理 return result return ラッパー関数名 @デコレーター名 def 修飾される側の関数(その引数) : 修飾される側の関数の処理
- コード例
def my_decorator(fn) : def _wrapper(*args, **kwargs) : print(f"my_decorator<prologue>:{fn.__name__=}:{args=}, {kwargs=}") result = fn(*args, **kwargs) print(f"my_decorator<epilogue>:{result=}") return result return _wrapper @my_decorator def calc(left, right) : return left + right print(f'''\ {calc(3, 5)=} {calc(right=8, left=9)=}''')
- 実行結果
my_decorator<prologue>:fn.__name__='calc':args=(3, 5), kwargs={} my_decorator<epilogue>:result=8 my_decorator<prologue>:fn.__name__='calc':args=(), kwargs={'right': 8, 'left': 9} my_decorator<epilogue>:result=17 calc(3, 5)=8 calc(right=8, left=9)=17
デコレターの文法は、以下のような糖衣構文です。
@my_decorator def fn(...): ... # は def fn(...): ... fn = my_decorator(fn) #と同じ
デコレーター・パターンは、オブジェクト指向プログラミングにおいて、同じクラスの他のオブジェクトの動作に影響を与えることなく、個々のオブジェクトに動的に動作を追加することができるデザインパターンです。 デコレーター・パターンは、単一責任原則(Single Responsibility Principle)を遵守するために有用で、独自の関心領域を持つクラス間で機能を分割することができます。 デコレーターの使用は、全く新しいオブジェクトを定義することなく、オブジェクトの動作を拡張することができるため、サブクラス化よりも効率的です。
Pythonのデコレーター構文は、デコレーター・パターンの言語支援と言えます。
global 文
[編集]ローカル変数や仮引数にグローバル変数がシャドーイングされた場合、 関数の中でも、
global 変数
と変数宣言することにより、その変数をグローバル変数として取り扱えます。
- コード例
a = 10 b = 11 def func(a) : global b print(f"func:{a=},{b=}") b = a * 2 print(f"func:{a=},{b=}") func(a) print(f"GLOBAL:{a=},{b=}")
- 表示結果
func:a=10,b=11 func:a=10,b=20 GLOBAL:a=10,b=20
C言語には、このような機能(関数内で変数をグローバル宣言する機能)は、ありません。 C++では、グローバル変数の参照に :: を前置することでシャドーイングを回避できます。
関数内で新たなグローバル変数を作る
[編集]- コード例
a = 10 def func(a) : global b # print(f"func:{a=},{b=}") ← この時点では b は未定義 b = a * 2 print(f"func:{a=},{b=}") func(a) print(f"GLOBAL:{a=},{b=}")
- 表示結果
func:a=10,b=20 GLOBAL:a=10,b=20
nonlocal文
[編集]nonlocal文は、内包関数から、自分を呼び出した関数のローカル変数にアクセスする仕組みです。 global文と違い、新たな変数を作ることはできません。
- コード例
def outer() : f = 25 def inner() : # 関数内関数 nonlocal f f = 13 return 1 inner() print(f) outer()
- 実行結果
13
- nonlocal の行をコメントにすると、結果は 「25」 に変わります。
クロージャ
[編集]クロージャ(関数クロージャ)は、外側の変数を格納する関数です。
- コード例
def f(z) : def _f() : nonlocal z z += 1 return z return _f q = f(-2) print(q()) print(q()) print(q()) print(q())
- 実行結果
-1 0 1 2
- _fをクロージャ、fをエンクロージャといいます。
この絵柄に見覚えがある人もいると思います。デコレーターの引数(デコレート対象の関数)はラッパー関数は格納した変数です。