Skip to content
This page has been auto-translated and may contain errors.View in English

関数

docs.scrimba.com

プログラムが大きくなると、同じロジックを複数の場所に書くことになります。関数を使えば、ロジックを一度書いて名前をつけ、どこからでも使えます。一か所で修正すれば、すべての呼び出しが自動的に修正されます。

関数はコード再利用と抽象化の主な単位です。ある動作をまとめて、名前をつけ、明確なインターフェース(パラメータと戻り値)を定義し、どこからでも呼び出せるようにします。関数の名前が良ければ、本体を読まなくても説明になります:validate_email() を見れば、コードブロックが何をするのか分かります。

Pythonでは関数は第一級オブジェクトです。つまり、他の値と同じように扱える値で、変数に割り当てたり、リストや辞書に保存したり、他の関数に引数として渡したり、関数から返したりできます。def がそのオブジェクトを作成し、現在のスコープ(その名前が見える領域)に名前をバインドします。関数を値として扱うことで、sorted(key=...) のようなパターンや小さなプラグインテーブルが実現でき、早い段階から慣れておく価値があります。

python
def greet(name):
    return f"Hello, {name}!"

print(greet("太郎"))   # "Hello, 太郎!"
print(greet("花子"))     # "Hello, 花子!"

一度書いて、どこからでも使い、一か所で修正します。

関数の定義

def キーワードで関数定義を始め、その後に名前、括弧、コロン、インデントされた本体が続きます。関数は呼び出されるまで何もしません。def で定義してから、() で名前を呼び出します。

def は関数オブジェクトを作成し、現在のスコープの与えられた名前にバインドするステートメントです。本体は定義時には実行されず、関数が呼び出されたときだけ実行されます。return ステートメントがない関数は暗黙的に None を返します。

def は関数オブジェクトを構築し、現在のスコープの名前にバインドします。本体はPythonが def を読むときには実行されず、関数を呼び出すときだけ実行されます。この分離は実際に重要です。関数本体内のタイプミス(存在しない名前など)は、関数が実際に呼び出されるまで静かに保たれるため、呼び出されたことのない関数は NameError を隠したまま、本番環境でその関数が初めて呼び出される日にだけ表面化することがあります。

python
def say_hello():
    print("Hello!")

say_hello()   # 関数を呼び出す
Juno関数の定義def name(): と書いて、その下に本体をインデントします。name() で呼び出すまで何もしません。def は準備をするだけです。return を忘れると、関数は静かに None を返します。これで何度も引っかかりました。
Juno関数の定義def は関数を作成して名前にバインドしますが、本体が実行されるのは呼び出すときだけです。return がなければ、関数は None を返します。結果が予期せず空で返ってくる理由を探すときに心に留めておきましょう。
Juno関数の定義 本体は `def` の時点では実行されず、関数を呼び出すときだけです。そのため、本体内の破れた名前は気づかないままいられます。`return` がない関数は `None` を返し、これが呼び出し元が値を期待するときの静かなバグになります。

パラメータと引数

パラメータは関数が期待する入力です。括弧内にリストアップします。関数を呼び出すとき、渡す値はパラメータに順番に合致します。

パラメータは関数のインターフェースを定義します。引数は呼び出し時に渡される具体的な値です。位置引数は位置で合致し、キーワード引数は名前で合致します。デフォルト値はパラメータをオプションにします。

呼び出し時に、位置引数は左から右に、キーワード引数は名前でバインドされます。同じパラメータを両方で渡すか、Pythonが配置できない場合は、呼び出しで TypeError が発生します。本体の内部ではなく呼び出しの時点です。Pythonは署名を制限することもできます。ベアの * はその後のすべてをキーワード限定にし(呼び出し元は名前を付ける必要があります)、/ はその前のすべてを位置限定にします。ブール値フラグと幅広い署名では、キーワード限定を優先してください。位置指定された True が間違った場所にあると、コンパイルはうまくいってもバグが出荷されます。

python
def greet(name, greeting):
    print(f"{greeting}, {name}!")

greet("太郎", "Hello")    # "Hello, 太郎!"
greet("花子", "Hi")         # "Hi, 花子!"

パラメータは関数定義内の名前です。引数は関数を呼び出すときに実際に渡す値です。実際には、これらの言葉は同じ意味で使われることが多く、区別が重要になるのは主にドキュメントを読むときです。

Junoパラメータと引数 パラメータは括弧内にリストアップした名前で、引数は実際に渡す値です。Pythonが左から右に一列に並べるので、渡す順番が着地する順番です。
Junoパラメータと引数 パラメータは定義内の名前で、引数は呼び出し時の値です。位置指定のものは左から右にバインドされるので、位置が契約になります。この2つの言葉は同じ意味で使われ、区別はドキュメントを読むときだけ重要です。
Junoパラメータと引数 位置引数は左から右に、キーワード引数は名前でバインドされ、衝突は呼び出しで TypeError を発生させます。ベアの `*` は呼び出し元が後に続くものに名前を付けることを強制し、これが関数の `True`/`False` フラグがそうでなければ呼び出しサイトでの推測ゲームになる場合の安い修正です。

デフォルト値

パラメータにデフォルト値を与えることができます。呼び出し元がその引数を指定しない場合、デフォルトが使用されます。デフォルト値を持つパラメータは、デフォルト値を持たないパラメータの後に来る必要があります。

デフォルト値はパラメータをオプションにします。デフォルト値は定義時に一度評価され、各呼び出しごとではありません。これはミュータブルなデフォルト値の場合に重要です。def f(items=[]) はすべての呼び出しで同じリストを共有します。修正は、デフォルトとして None を使い、関数本体内でリストを作成することです。

詳細で引っかかるのは、デフォルト値が一度、def が実行されるときに構築され、その値を使用するすべての呼び出しで共有されることです。0"Hello" のようなイミュータブルなデフォルト値(その場で変更できない値)の場合は問題ありません。リストや辞書のようなミュータブルなデフォルト値(その場で変更できる値)の場合は罠です。デフォルトにフォールバックするすべての呼び出しが同じオブジェクトを再利用するためです。

python
def add_item(item, items=[]):   # リストは一度作成され、永遠に共有される
    items.append(item)
    return items

add_item("a")   # ['a']
add_item("b")   # ['a', 'b']  <- 新しいリストではない

修正は標準的なものです。デフォルトを None に設定し、本体内に新しいコンテナを構築します。

python
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items
python
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("太郎")           # "Hello, 太郎!"
greet("太郎", "Hi")     # "Hi, 太郎!"

デフォルト値を持つパラメータは、デフォルト値を持たないパラメータの後に来る必要があります。

Junoデフォルト値 パラメータにデフォルトを指定すると、オプションになり、呼び出し元はそれをスキップできます。デフォルト値を持つものは必須のものの後に来る必要があり、この順序はPythonが強制するルールです。すべての呼び出しで同じ値を書く手間が省けます。
Junoデフォルト値 デフォルトはパラメータをオプションにし、必須のものの後に来る必要があります。引っかかるのは、デフォルト値が定義時に一度評価されるため、`[]` のようなミュータブルなデフォルトを使わないことです。デフォルトを `None` に設定して、本体内でリストを構築してください。
Junoデフォルト値 デフォルト値は `def` の時点で一度構築され、呼び出し間で共有されるため、`items=[]` は呼び出し間で静かに蓄積します。デフォルトを `None` に設定し、本体で新しいものを作成します。これはコードレビューでほぼすべての他のPythonのミスよりも多く表れます。

キーワード引数

関数を呼び出すときに、引数に名前を付けることができます。これにより、特に多くのパラメータを持つ関数で呼び出しが読みやすくなり、任意の順序で引数を渡すことができます。

キーワード引数は関数呼び出しを自己説明的にします。位置引数とキーワード引数を混ぜることができます。位置引数が最初に来る必要があります。ブール値フラグを持つ関数や、同様の型の多くのパラメータを持つ関数では、キーワード引数は、引数を間違った順序で渡すことによる静かなミスを防ぎます。

キーワード引数は名前でバインドされるため、読みやすく、位置を覚える必要がありません。呼び出しサイトの規則:位置引数が最初に来て、その後がキーワード引数で、同じパラメータに両方で名前を付けると TypeError が発生します。デザイン上の動きは反対です。独自の署名に ベアの * を入れて、呼び出し元がパラメータに名前を付ける必要があるようにします(def connect(host, *, timeout=30))。これは何も費用がかからず、不透明な connect("db", 30)connect("db", timeout=30) に変え、後で誰かが中間にパラメータを追加したときにも生き残ります。

python
def describe_player(name, score, level):
    print(f"{name} | Score: {score} | Level: {level}")

describe_player("太郎", 87, 5)                        # 位置指定
describe_player(name="太郎", level=5, score=87)       # キーワード、任意の順序
describe_player("太郎", level=5, score=87)            # ミックス:位置指定が最初
Junoキーワード引数 呼び出しで引数に名前を付ける(score=87)と、呼び出しが自分自身を説明し、任意の順序で渡せます。1つのルール:位置引数は名前付きのものの前に来る必要があります。多くのパラメータを持つ関数に最適です。
Junoキーワード引数 引数に名前を付けると、呼び出しが自己説明的になり、順序に依存しなくなります。位置引数が最初に必須です。幅広い署名またはブール値フラグについては、キーワード引数を優先してください。呼び出しを読むために誰も位置をカウントする必要がないようにするためです。
Junoキーワード引数 キーワード引数は名前でバインドされ、位置指定のものの後に来る必要があります。本当の支点は独自の署名です。ベアの `*` は呼び出し元に後続するものに名前を付けることを強制し、誰かが中間に新しいパラメータを挿入した後でも呼び出しが読みやすく保たれます。

戻り値

return は値を呼び出し元に送り返します。return がない場合、関数は None を返します。return が実行されると、関数は直ちに終了します。そのブロック内の後続コードはスキップされます。

return は関数を終了し、値を呼び出し元に渡します。明示的な return がない関数は暗黙的に None を返します。return は関数本体のどこにでも表れることができ、複数回使用される場合があります。到達した最初の return が関数を終了します。これにより、早期リターンはガード句に有用です。

return は値を呼び出し元に渡し、その場で関数を終了します。return なしで関数の終わりまで走ると None を返します。複数のリターンはスタイルとして良く、臭いではありません。失敗またはエッジケースのための早期 return は上の方に(「ガード句」パターン)が、本体全体を1つの大きな if の内側にネストするよりもはるかに読みやすいです。知っておくべき1つの微妙な点:try ブロック内の return はまず一致する finally ブロックを実行するため、finally でクリーンアップを行うと、早期リターンでもクリーンアップが起こります。

python
def add(a, b):
    return a + b

result = add(3, 4)   # result = 7
print(result)

return はまた、関数を直ちに終了します。そのブロック内の後続コードは実行されません。

Juno戻り値return は関数を呼び出した人に値を返し、関数はそこで停止します。それ以降のコードはスキップされます。return を省略すると `None` が返されます。print は値を表示し、return は使用するためにそれを返します。この違いを感じるのに時間がかかりました。
Juno戻り値return は値を渡して直ちに終了します。`return` がなければ `None` を返します。複数のリターンを持つことができ、エッジケースの早期 `return` は上の方が、すべてをラップするのが通常1つの大きな `if` より読みやすいです。
Juno戻り値return はその場で終了し、終わりまで走ると `None` を返します。深いネストの代わりに、ガード句として早期リターンに頼りましょう。そして `try` 内の `return` は `finally` ブロックを実行することを思い出してください。そのため、クリーンアップはそこで早期終了でも起こります。

複数の値を返す

Pythonは複数の値をコンマで区切って返すことができます。呼び出し元は、タプルとしてそれらを受け取り、1行で別の名前に展開することができます。

複数の値をコンマで返すと、タプルに詰め込まれます。呼び出し元は一致する名前で展開します。これは自然に複数以上の結果を生成する関数のためのPythonic方法です。これは特別な機能ではありません。タプルの詰め込みと展開です。

return a, b は値を タプル に詰め込みます。呼び出し元は low, high = f() で引き離します。2つか3つの結果ですっきり読めます。それを超えると、位置が負債になります。関数を呼び出す人は、3番目の項目がカウントであるか平均であるかを覚えていません。入れ替わったペアは静かなバグです。その時点で代わりに何か名前の付いたものを返す場合は、NamedTuple または @dataclass を使い、呼び出し元が result[2] ではなく result.average に手を伸ばすようにします。tuple[int, str] のように戻り値に注釈を付けて、型チェッカーが呼び出し元に値の数を展開させる間違いを捉えることができるようにします。

python
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([3, 7, 1, 9, 4])
print(low, high)   # 1 9

low, high = ... 構文は展開です。Pythonは各戻り値を対応する名前に割り当てます。

Juno複数の値を返す 値をコンマで区切ると、関数はそれらをタプルとして一緒に返します。呼び出し元は1行で low, high = min_max(...) で、値ごとに1つの名前を捕捉します。関数が自然に複数の答えを生成するときはいつでも役立ちます。
Juno複数の値を返す コンマはタプルに値を詰め込み、呼び出し元は一致する名前で展開します。これはタプルの詰め込みと展開に他なりません。特別な機能ではありません。2つか3つの結果ですっきりです。
Juno複数の値を返す コンマリターンはタプルを詰め込みます。2つか3つの値で問題ありません。それを超えると、位置が誰も読めない罠になるため、`NamedTuple` または `@dataclass` を返し、呼び出し元がインデックスをカウントする代わりに `result.average` を使えるようにします。

スコープ

関数内に作成された変数は、その関数内にのみ存在します。外部からそれらを見ることはできません。すべての関数の外部で定義された変数は至るところで見えますが、明示的な宣言なしに関数内からそれらを変更することはできません。

関数内に作成された名前はローカルです。そこにのみ存在し、関数が返ったときに消えます。ファイルのトップレベルで定義された名前はグローバルです。関数内からグローバルを読む場合は儀式なしで機能しますが、1つに割り当てるには global name が必要です。それ以外の場合、Pythonは割り当てをグローバルをシャドウする新しいローカルの作成として扱います。実際には、ほぼ global を望むことはありません。値を引数として渡し、結果を return で返すことで、関数の効果は呼び出しサイトで見える状態を保ちます。

Pythonは LEGB ルールで名前を解決します。4つの場所を順番にチェックします。Local(この関数)、Enclosing(この関数をラップする外側の関数)、Global(モジュール)、Built-in(len のような名前)。読むことはその鎖を走査します。割り当ては、あなたが別途言わない限り、常にローカルを作成します。この非対称性がバグのソースです。global count 行なしにグローバルに対して count += 1UnboundLocalError を発生させます。割り当てが関数全体の count をローカルとしてマークするため、読むことは値のローカル存在の前に起こります。global name は割り当てをモジュールレベルに戻すためにオプトアウトします。nonlocal name は代わりに最も近い外側の関数をターゲットにします。これが、クロージャが作成した関数の変数を更新する方法です。両方を臭いとして扱います。外側の状態を変更するために手を出す関数は、入力を取り出力を返す関数よりもテストが難しいです。

Junoスコープ 関数内に作成された変数はそこにのみ存在し、外部はそれを見ることができません。外部の変数を内部から読むことはできますが、1つを変更するには最初に global 行が必要です。それをほぼ望むことはありません。値を渡し、結果を返します。関数はシンプルに追うことができます。
Junoスコープ 関数内に作成された名前はローカルで、関数が返ったときに消えます。グローバルを読むのは無料です。1つに書くには `global name` が必要です。そうしないと、Pythonはそれをシャドウするローカルを作成します。引数で値を取って `return` で結果を出すことを優先してください。関数の効果は呼び出しのままで見えます。
Junoスコープ ルックアップの場合はLEGB。割り当ては、`global` または `nonlocal` を宣言しない限り、常にローカルを作成します。これが `count += 1` がグローバルで `UnboundLocalError` をスローする理由です。両方のキーワードは臭いです。外側の状態を変更する関数は、入力と出力を返す関数よりもテストが難しいです。
python
def calculate():
    result = 42   # この関数のローカル
    return result

calculate()
print(result)   # NameError、resultはここに存在しません
python
count = 0

def increment():
    global count    # グローバルを変更したいことを宣言する
    count += 1

increment()
print(count)   # 1

global を使用することは最後の手段であるべきです。コードを推論するのが難しくなります。値を渡し、返すことを優先してください。スコープは、変数と型の章で説明されている割り当てが名前を値にバインドする方法に直接構築されます。

*args と **kwargs

時々、関数が受け取る引数の数は分かりません。*args は任意の数の位置引数をタプルに集めます。**kwargs は任意の数のキーワード引数を辞書に集めます。名前 argskwargs は慣習です。星が重要です。

*args は超過の位置引数をタプルに集めます。**kwargs は超過のキーワード引数を辞書に集めます。両方とも、通常のパラメータと組み合わせることができます。通常のパラメータが最初に来て、次に *args、次にキーワード限定パラメータ、次に **kwargs。それらは、引数を別の関数に渡すラッパー関数に有用です。

*args は左側の位置引数を tuple に集めます。**kwargs は左側のキーワード引数を dict に集めます。鏡像は呼び出しサイトで起こります。func(*some_list) はリストを位置引数に広げ、func(**some_dict) は辞書をキーワード引数に広げます。その対称性がパススルーラッパーを機能させます。def timed(*args, **kwargs): return inner(*args, **kwargs) はパラメータに名前を付けることなく、それが得たものを転送します。有用ですが、読める署名が失われます。すべてを *args, **kwargs に飲み込むラッパーは、呼び出し元と型チェッカーに、受け入れるものについて何も示しません。実際に任意の引数を転送する必要があるときに使用してください。通常の関数のデフォルトシェイプとしてではなく。

python
def total(*args):
    return sum(args)

total(1, 2, 3)          # 6
total(1, 2, 3, 4, 5)   # 15
python
def display(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

display(name="太郎", score=87, level=5)

通常のパラメータと混ぜることができます。通常のパラメータが最初に来ます。

python
def describe(title, *tags, **metadata):
    print(f"{title} | tags: {tags} | meta: {metadata}")

describe("Python intro", "beginner", "python", author="太郎", year=2024)
Juno*args と **kwargs*args は追加の位置引数の任意の数をタプルとしてすくい上げます。**kwargs は追加の名前付きをを辞書としてすくい上げます。言葉 `args` と `kwargs` は慣習に過ぎません。`*` と `**` が本当の仕事をします。いくつのものが来るかを事前に言えないときに役立ちます。
Juno*args と **kwargs `*args` は超過の位置引数をタプルに集めます。`**kwargs` は超過のキーワード引数を辞書に集めます。署名内の順序:通常のパラメータ、次に `*args`、次にキーワード限定、次に `**kwargs`。ラッパー関数が引数を別の関数に直接転送するのに最も有用です。
Juno*args と **kwargs `*` と `**` は署名で集めて、呼び出しサイトで広げます。これが、ラッパーがすべてを `inner(*args, **kwargs)` で転送できることです。コストは、呼び出し元に何も伝えない署名です。本当のパススルーに保管し、通常の関数ではありません。

ドキュメント文字列

ドキュメント文字列は、関数の上部にある、その関数がしていることを説明する文字列です。Pythonエディタやツールはそれを使用して、関数呼び出しにホバーするときにヘルプを表示します。三重引用符を使用し、単純な関数の場合は1行を書きます。

ドキュメント文字列は関数本体の最初のもので、三重引用符の文字列です。ツールがそれを読みます。help(func) はそれを印刷し、ホバーするとエディタがそれを表示するため、関数がしていることを忘れたときに正確に支払われます。慣習は1つの概要行です。その後、必要に応じてより多くの詳細が続く空白行です。複数の場所から呼び出されるもので必須です。名前がすべてを言う気づか付きのヘルパーではオプションです。

ドキュメント文字列は関数、クラス、またはモジュール本体の最初のステートメントで、関数がしていることと、それが返すものを言う必要があります。既に見えるパラメータリストを言い直さないでください。署名が明らかでない契約上のものであるすべてのものに利益をもたらします。エッジケース、発生するもの、空の入力がしていることです。型ヒントが既に署名内で型を持つときに、正式なarg-by-argブロックをスキップしてください。その詳細は、ドキュメント文字列でなく、ひどく古い散文で、ヒントに属します。目標は、6か月前に書いた呼び出しをデバッグしている午前2時に読みたい行です。

python
def normalise(value, min_val, max_val):
    """Scale a value to the 0-1 range given the known min and max."""
    return (value - min_val) / (max_val - min_val)
python
def build_url(base, version, resource, *, secure=True):
    """
    Build an API endpoint URL.

    Returns a fully-qualified URL string. If secure is False,
    the URL will use http instead of https.
    """
    scheme = "https" if secure else "http"
    base = base.replace("https://", "").replace("http://", "")
    return f"{scheme}://{base}/{version}/{resource}"

目的が名前と署名から明確でない関数のドキュメント文字列を書きます。

Junoドキュメント文字列 ドキュメント文字列は、関数の最上部の三重引用符の文字列で、何をしているかを説明します。後で関数にホバーするとエディタがそれを表示し、そのときに嬉しいです。名前と入力が既に目的を明確にしていない場合は、いつでも1つを書きます。
Junoドキュメント文字列 本体の最初の行、三重引用符です。`help()` とエディタ表示がホバーするときに表面化します。ほぼすべての関数を1つの概要行でカバーし、より詳細なコンテンツを下に追加します。複数の場所から呼び出されるもので必須です。名前がすべてを言うワンライナーではスキップ可能です。
Junoドキュメント文字列 関数がしていることと、何を返すかと、エッジケースを言います。既に見えるパラメータリストの散文で言い直さないでください。型ヒントが既に型を持つときに、ドキュメント文字列でなく、ヒントに属します。目標は、6か月前に書いた呼び出しをデバッグしている午前2時に読みたい行です。

型ヒント

型ヒントにより、関数が期待する型と返す型に注釈を付けることができます。Pythonは実行時にそれらを強制しません。しかし、エディタはそれらを使用して、何かを実行する前にミスを捕捉します。-> はコロンの前に戻り型を指定します。

型ヒントは、ツールが検証するドキュメンテーションです。エディタと型チェッカー(mypy、pyright)はそれらを使用して、実行時の前に型のミスマッチを捕捉します。標準的なPythonでは実行時効果がありません。-> None は、戻り値がない関数の正しい注釈です。汎用コンテナについては、list[int]dict[str, int](Python 3.9+)を使用してください。

型ヒントは実行時には何も変更しません。Pythonは、それらなしで、または、なしで、同じように走ります。その全体の価値はプログラムの外側のレイヤーです。型チェッカー(mypy または pyright)がそれを実行する前にコードを読んでいます。エディタがあなたがそれを入力するときに悪い呼び出しをフラグしています。そのため、嘘をついた型ヒントは、チェッカーが信頼するため、ヒントなしより悪いです。list[int]dict[str, int]str | None を直接使用してください(最新のPythonで typing インポートは不要)。何も返さない関数に -> None で注釈を付けます。これも、呼び出しを式で呼び出す(x = log(...))ことが間違っていることを通知します。ペイオフは多くの場所から呼び出される関数にあります。チェッカーは、実行時に3フレーム深い混乱している失敗として浮かぶ代わりに、呼び出し元が間違った型を渡すことをキャッチします。

python
def greet(name: str, score: int) -> str:
    return f"{name} scored {score}"
python
def log(message: str) -> None:
    print(f"[LOG] {message}")
python
def top_scores(scores: list[int], n: int) -> list[int]:
    return sorted(scores, reverse=True)[:n]

型ヒントはオプションですが、複数の場所から呼び出される関数に価値があります。それらは、ツールが検証できるドキュメンテーションです。

Juno型ヒント 型ヒントは、関数が何を受け取って返すかをメモします。`name: str` と `-> str` のような戻りに対してです。Pythonは実行するときに強制しません。しかし、エディタはそれらを読み、実行の前に警告します。ツールが確認できるメモとして考えてください。
Juno型ヒント ヒントはドキュメンテーションです。型チェッカーが検証でき、実行時効果がありません。`-> None` は関数が何も返さないときの正しい注釈です。`list[int]` / `dict[str, int]` は最新のPythonで `typing` インポートなしで機能します。複数の場所から呼び出されるもので価値があります。
Juno型ヒント ヒントは実行時に何もしません。値はチェッカーとエディタが読むことです。そのため、嘘をついたヒントはなしより悪いです。`str | None` と `list[int]` を直接使用してください。`-> None` は戻りなし用。勝利は、3フレーム深い失敗の前に、呼び出し元が間違った型を渡すことをキャッチすることです。

値としての関数

Pythonの関数は、文字列や数値のような値です。変数に割り当てたり、他の関数に渡したりできます。これは sorted()key= 関数を受け入れる方法です。

関数は第一級オブジェクトです。型(function)を持ち、変数とコレクションに保存でき、引数として渡したり、値として返したりできます。これは、sorted(key=...)map()filter() のような高階関数の基礎です。

関数はコピーではなく参照で渡すオブジェクトです。そのため、1つを別の関数に引き渡すのは安いです。これから生じるパターンはクロージャです。別の関数の内側で定義された関数で、外側の関数が返った後でも外側の関数の変数に到達する関数です。その場で設定された関数を構築する方法です。make_multiplier(3) は3で乗算する関数を返し、3がキャプチャされて保有されています。実際のコードで2つの警告があります。クロージャはキャプチャ時に値ではなく変数をキャプチャします。これはループでクロージャを構築する人をキャッチします(すべてがループの最終値を見ます)。多くの戻った関数をスタックすると、呼び出しパスが追跡するのが難しくなります。クロージャが読みやすいところで使用し、状態が成長するときはクラスに手を伸ばします。

python
def double(x):
    return x * 2

def apply(func, value):
    return func(value)

apply(double, 5)   # 10

関数を引数として渡すことは sorted()map()filter() で絶えず表れます。また、ラムダと内包表記の章でそれを見ます。

Juno値としての関数 関数は文字列や数値のような値です。変数に保存し、別の関数に渡すことができます。これが、関数を `key=` として `sorted()` に渡すことができる理由です。最初は奇妙に感じたが、その後、クリックして至る所に表れました。
Juno値としての関数 関数は第一級オブジェクトです。割り当てし、リストに保存し、渡し、返す。それが `sorted(key=...)`、`map()` と `filter()` の背後にあり、後で結線するすべてのコールバックの背後にあります。
Juno値としての関数 関数を渡すことはコピーではなく参照で、クロージャにより、内側の関数は外側の関数が返った後でも外側の関数の変数に到達し続けます。変数をキャプチャし、値ではなく、ループでクロージャを構築するたびに噛みます。キャプチャされた状態が成長するときは、クラスがより読みやすいです。

実装例

一緒に動作する2つの関数:letter_grade はスコアを文字に変換し、summarise はリスト内のすべてのスコアに対してそれを呼び出します。

python
def letter_grade(score: int) -> str:
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"

def summarise(scores: list[int]) -> None:
    total = sum(scores)
    avg = total / len(scores)
    grades = []
    for s in scores:
        grades.append(letter_grade(s))
    print(f"Average: {avg:.1f}")
    print(f"Grades: {', '.join(grades)}")

summarise([87, 92, 74, 65, 91])

ログフォーマッタとそれを使用するファイルプロセッサ。dry_run デフォルトパラメータで、明示的に無効にしない限り副作用を防ぎます。

python
def format_log(level: str, message: str) -> str:
    return f"[{level.upper():5}] {message}"

def process_file(path: str, dry_run: bool = True) -> bool:
    print(format_log("info", f"Processing {path}"))
    if dry_run:
        print(format_log("info", "Dry run, no changes made"))
        return True
    return True

process_file("report.csv")
process_file("report.csv", dry_run=False)

その上に構築された単一値ノーマライザーと列ノーマライザー。型ヒントとドキュメント文字列があります。列関数は一度範囲を計算し、各項目のスカラー関数を再利用します。

python
def normalise(value: float, min_val: float, max_val: float) -> float:
    """Scale value to the 0-1 range given known min and max."""
    if max_val == min_val:
        return 0.0
    return (value - min_val) / (max_val - min_val)

def normalise_column(values: list[float]) -> list[float]:
    """Normalise an entire column of values."""
    lo, hi = min(values), max(values)
    return [normalise(v, lo, hi) for v in values]

raw = [10.0, 25.0, 5.0, 40.0, 15.0]
print(normalise_column(raw))

型ヒントはここで2つの目的を果たします。関数が期待するものについてのドキュメントと、型チェッカーが文字列のリストを渡す呼び出し元をキャッチすることができます。