Pythonの関数とは、ひとまとまりの処理に名前を付けて、何度でも呼び出せるようにしたものです。 def 関数名(引数): と書いて行末にコロンを置き、次の行から字下げして処理を書きます。結果を呼び出し元に返すときは return を使い、return を書かなかった関数は None を返します。

この記事では、defの基本から、引数の種類(位置・キーワード・デフォルト・*args・**kwargs・/・*)、戻り値、複数の戻り値の受け取り方、型ヒントまでを1ページにまとめました。コードはすべて Python 3.14.7 で実行し、出力をそのまま載せています。 デフォルト引数にリストを置いたときの動きと、引数の過不足で出るTypeErrorは、実際に再現してから直し方を示します。

Python 関数defの早見表

引数の種類の早見表(書き方と呼び出し例)

種類 定義の書き方 呼び出し例 ポイント
位置引数 def f(a, b): f(1, 2) 順番で対応する
キーワード引数 def f(a, b): f(b=2, a=1) 名前で対応する。順番は自由
デフォルト引数 def f(a, b=10): f(1) / f(1, 5) 省略すると既定値。既定値なしの引数より後ろに書く
可変長の位置引数 def f(*args): f(1, 2, 3) 関数の中ではタプルで受け取る
可変長のキーワード引数 def f(**kwargs): f(x=1, y=2) 関数の中では辞書で受け取る
位置専用引数 def f(a, b, /): f(1, 2) / より前は名前で渡せない(3.8以降)
キーワード専用引数 def f(a, *, key): f(1, key=2) * より後ろは名前で渡す
リストを展開して渡す def f(a, b): f(*[1, 2]) 呼び出し側の * は展開
辞書を展開して渡す def f(a, b): f(**{"a": 1, "b": 2}) 呼び出し側の ** は展開

定義に並べる順番は 「位置専用 → / → 通常の引数 → *args(または *)→ キーワード専用 → **kwargs」 です。

戻り値の早見表

やりたいこと 書き方 受け取り方
値を1つ返す return x y = f()
複数の値を返す return a, b a, b = f()(実体は1つのタプル)
何も返さない return を書かない/return だけ 戻り値は None
途中で終える 条件の中で return それ以降の行は実行されない
名前付きで複数返す NamedTuple や dataclass を返す r.maximum のように名前で取り出す
型を明示する def f(a: int) -> str: 戻り値なしは -> None

よく出るエラーの早見表

エラーメッセージ(抜粋) 原因
missing 1 required positional argument: 'height' 引数が足りない
takes 2 positional arguments but 3 were given 引数が多い
got an unexpected keyword argument 'depth' 定義にない名前を渡した
got multiple values for argument 'width' 同じ引数を位置と名前の両方で渡した
SyntaxError: positional argument follows keyword argument キーワード引数の後ろに位置引数を書いた
SyntaxError: parameter without a default follows parameter with a default 既定値ありの引数の後ろに既定値なしの引数を定義した

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python 関数defの書き方|引数・デフォルト値・戻り値と複数の戻り値」

defで関数を定義する基本の書き方

def greet(name):
    """名前を受け取り、あいさつ文を返す。"""
    return f"こんにちは、{name}さん"

message = greet("佐藤")
print(message)
print(greet("鈴木"))
print(greet.__doc__)
こんにちは、佐藤さん
こんにちは、鈴木さん
名前を受け取り、あいさつ文を返す。
部品 例 説明
def def 関数を定義するキーワード
関数名 greet 小文字とアンダースコアで付けるのが標準的(例:get_total)
引数(仮引数) (name) 受け取る値の名前。なければ () だけ書く
コロン : 行末に必ず付ける
docstring """…""" 関数の説明。書かなくても動く。help() やエディタで表示される
本体 字下げした行 半角スペース4つで字下げする
return return … 呼び出し元に値を返して関数を終える

関数は定義しただけでは実行されません。 greet("佐藤") のように、関数名のあとにかっこを付けて呼び出したときに初めて本体が動きます。定義のときに書く name を「仮引数」、呼び出すときに渡す "佐藤" を「実引数」と呼びます。

定義と呼び出しでつまずきやすい点

def greet(name):
    return f"こんにちは、{name}さん"

print(greet)        # かっこを忘れると関数オブジェクトそのもの
print(greet("佐藤"))

def todo():
    pass            # 中身をあとで書くときは pass

print(todo())

def outer():
    def inner():            # 関数の中に関数を定義できる
        return "inner"
    return inner().upper()

print(outer())
<function greet at 0x7f27c2f2f480>
こんにちは、佐藤さん
None
INNER

(0x… の部分は実行のたびに変わります。)

書き方 起きること 直し方
print(greet) 関数オブジェクトの表示になる greet("佐藤") とかっこを付けて呼ぶ
定義より前で呼ぶ NameError: name 'add' is not defined 呼び出しを定義より後ろに書く
行末のコロンがない SyntaxError: expected ':' def add(a, b): とコロンを付ける
本体を字下げしていない IndentationError: expected an indented block after function definition on line 1 スペース4つで字下げする
本体が空 同じIndentationError pass を置く

Pythonはファイルを上から順に実行するため、def の行に到達する前にその関数を呼ぶとNameErrorになります。

引数の渡し方:位置引数とキーワード引数

def describe(name, age, city):
    return f"{name}({age}歳・{city})"

print(describe("佐藤", 30, "東京"))                 # 位置引数: 順番で対応
print(describe(city="大阪", name="鈴木", age=25))   # キーワード引数: 名前で対応
print(describe("田中", city="福岡", age=41))        # 混在: 位置引数を先に書く
佐藤(30歳・東京)
鈴木(25歳・大阪)
田中(41歳・福岡)
  • 位置引数は、書いた順番で仮引数に対応します。
  • キーワード引数は 名前=値 の形で渡し、順番を入れ替えられます。
  • 両方を混ぜるときは、位置引数を先、キーワード引数を後に書きます。

逆の順番で書くとSyntaxErrorになります。

def area(width, height):
    return width * height

print(area(width=3, 4))
  File "c12.py", line 4
    print(area(width=3, 4))
                         ^
SyntaxError: positional argument follows keyword argument

キーワード引数で呼び出しを読みやすくする

TrueやFalseを位置引数で並べると、呼び出し側だけを見ても意味が読み取りにくくなります。

def create_user(name, email, is_admin=False, notify=True):
    return f"{name} <{email}> admin={is_admin} notify={notify}"

# 位置引数だけ: 何が True で何が False か読み取りにくい
print(create_user("佐藤", "sato@example.com", False, True))
# キーワード引数: 呼び出し側を読むだけで意味がわかる
print(create_user("佐藤", "sato@example.com", is_admin=False, notify=True))

def create_user2(name, email, *, is_admin=False, notify=True):   # * 以降はキーワード必須
    return f"{name} <{email}> admin={is_admin} notify={notify}"

print(create_user2("鈴木", "suzuki@example.com", notify=False))
佐藤 <sato@example.com> admin=False notify=True
佐藤 <sato@example.com> admin=False notify=True
鈴木 <suzuki@example.com> admin=False notify=False

デフォルト値(デフォルト引数)の書き方

仮引数に 名前=値 と書くと、呼び出しで省略されたときの既定値になります。

def price_with_tax(price, rate=0.1, rounding=True):
    total = price * (1 + rate)
    return round(total) if rounding else total

print(price_with_tax(1000))                    # rate と rounding は既定値
print(price_with_tax(1000, 0.08))              # rate だけ指定
print(price_with_tax(980, rounding=False))     # 途中を飛ばすときはキーワードで
print(price_with_tax.__defaults__)             # 既定値は関数オブジェクトが持っている
1100
1080
1078.0
(0.1, True)

既定値は関数オブジェクトの __defaults__ に保存されています。この「関数が既定値を持っている」という仕組みが、次に説明するリストの罠につながります。

既定値ありの引数は後ろに書く

def connect(host="localhost", port):
    return f"{host}:{port}"
  File "c13.py", line 1
    def connect(host="localhost", port):
                                  ^^^^
SyntaxError: parameter without a default follows parameter with a default

既定値なしの引数を先、既定値ありの引数を後に並べます。def connect(port, host="localhost"): と直せば定義できます。

デフォルト引数にリストを置く罠

既定値に空のリスト [] を置くと、呼び出すたびに新しいリストが用意されるように見えますが、実際の動きはちがいます。

def add_item(item, items=[]):       # 直す前: 既定値にリストを置く
    items.append(item)
    return items

print(add_item("りんご"))
print(add_item("みかん"))            # 新しいリストのつもりが、前回の続きになる
print(add_item("ぶどう"))
print("既定値の中身:", add_item.__defaults__)

def add_item_fixed(item, items=None):   # 直した後: None を既定値にして中で作る
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item_fixed("りんご"))
print(add_item_fixed("みかん"))
print(add_item_fixed("ぶどう", ["もも"]))
print("既定値の中身:", add_item_fixed.__defaults__)
['りんご']
['りんご', 'みかん']
['りんご', 'みかん', 'ぶどう']
既定値の中身: (['りんご', 'みかん', 'ぶどう'],)
['りんご']
['みかん']
['もも', 'ぶどう']
既定値の中身: (None,)

直す前の関数は、2回目の呼び出しで ['りんご', 'みかん'] が返っています。__defaults__ を見ると、既定値として保存されているリストそのものに要素が追加され続けていることがわかります。

理由は、デフォルト値が「関数を定義したとき」に1回だけ評価されるからです。Python公式ドキュメント(言語リファレンスの関数定義、今回3.14.8版のページで確認)にも、デフォルト引数値は関数定義が実行されるときに評価され、呼び出しのたびに同じ値が使われること、リストや辞書を既定値にする場合は None を既定値にして関数の中で判定する書き方が示されています。

既定値にしたいもの 書き方
空のリスト items=None → 中で if items is None: items = []
空の辞書 options=None → 中で if options is None: options = {}
現在時刻 now=None → 中で if now is None: now = datetime.datetime.now()
数値・文字列・タプル・None・True/False そのまま既定値に書いてよい(変更できない値のため)

「定義時に1回だけ」という性質は、リスト以外でも確かめられます。

import datetime, time

def stamp(now=datetime.datetime.now()):      # def を実行した時点の時刻で固定される
    return now.strftime("%H:%M:%S.%f")

def stamp_fixed(now=None):
    if now is None:
        now = datetime.datetime.now()        # 呼び出すたびに取得
    return now.strftime("%H:%M:%S.%f")

a = stamp(); b = stamp_fixed()
time.sleep(0.5)
c = stamp(); d = stamp_fixed()
print("stamp       同じ値か:", a == c)
print("stamp_fixed 同じ値か:", b == d)

counter = 0
def next_id():
    global counter
    counter += 1
    return counter

def make(id=next_id()):       # next_id() は def の時点で1回だけ呼ばれる
    return id

print(make(), make(), make(), "counter =", counter)
stamp       同じ値か: True
stamp_fixed 同じ値か: False
1 1 1 counter = 1

0.5秒待ってから呼んでも stamp() は同じ時刻を返し、make() は3回呼んでも next_id() が1回しか実行されていません。

*argsと**kwargs:個数が決まっていない引数

def total(*args):
    print("  args =", args, type(args).__name__)
    return sum(args)

def show(**kwargs):
    print("  kwargs =", kwargs, type(kwargs).__name__)

def report(title, *args, sep=", ", **kwargs):
    return f"{title}: {sep.join(map(str, args))} {kwargs}"

print(total(1, 2, 3))
print(total())
show(name="佐藤", age=30)
print(report("点数", 80, 95, sep=" / ", unit="点"))

# 呼び出し側の * と **: リストや辞書を展開して渡す
nums = [10, 20, 30]
opts = {"name": "鈴木", "age": 25}
print(total(*nums))
show(**opts)
  args = (1, 2, 3) tuple
6
  args = () tuple
0
  kwargs = {'name': '佐藤', 'age': 30} dict
点数: 80 / 95 {'unit': '点'}
  args = (10, 20, 30) tuple
60
  kwargs = {'name': '鈴木', 'age': 25} dict
記号 定義側での意味 呼び出し側での意味
* 残りの位置引数をタプルにまとめる(*args) リストやタプルを位置引数に展開する
** 残りのキーワード引数を辞書にまとめる(**kwargs) 辞書をキーワード引数に展開する

args と kwargs という名前は慣習で、*values や **options のように別の名前でも動きます。*args の後ろに書いた引数(上の例の sep)は、キーワードでしか渡せなくなります。

位置専用引数(/)とキーワード専用引数(*)

定義の中に / と * を単独で書くと、引数の渡し方を制限できます。

def move(x, y, /, speed=1, *, relative=False):
    #    ~~~~     ~~~~~~~     ~~~~~~~~~~~~~~
    #  位置専用  どちらでも  キーワード専用
    return f"x={x}, y={y}, speed={speed}, relative={relative}"

print(move(10, 20))
print(move(10, 20, 3))
print(move(10, 20, speed=3, relative=True))

for label, call in [
    ("x= をキーワードで渡す", lambda: move(x=10, y=20)),
    ("relative を位置で渡す", lambda: move(10, 20, 3, True)),
]:
    try:
        call()
    except TypeError as e:
        print(f"{label} -> TypeError: {e}")
x=10, y=20, speed=1, relative=False
x=10, y=20, speed=3, relative=False
x=10, y=20, speed=3, relative=True
x= をキーワードで渡す -> TypeError: move() got some positional-only arguments passed as keyword arguments: 'x, y'
relative を位置で渡す -> TypeError: move() takes from 2 to 3 positional arguments but 4 were given
区切り 対象 渡し方 使いどころ
/ より前 位置専用引数 位置でのみ 引数名に意味がない(x, y など)。あとから引数名を変えても呼び出し側に影響しない
/ と * の間 通常の引数 位置でも名前でも 多くの引数はここ
* より後ろ キーワード専用引数 名前でのみ TrueやFalseのオプション、意味を取りちがえやすい値

標準ライブラリの inspect モジュールを使うと、それぞれの引数がどの種類かを調べられます。

import inspect

def move(x, y, /, speed=1, *args, relative=False, **kwargs):
    pass

for p in inspect.signature(move).parameters.values():
    print(f"{p.name:<9} {p.kind.description}")
print(inspect.signature(move))
x         positional-only
y         positional-only
speed     positional or keyword
args      variadic positional
relative  keyword-only
kwargs    variadic keyword
(x, y, /, speed=1, *args, relative=False, **kwargs)

どの種類を選ぶかの判断表

状況 選ぶ書き方
必ず渡してほしい値が1〜3個 通常の引数(既定値なし)
ほとんどの場合に同じ値でよい デフォルト引数
TrueやFalseの切り替え、オプションが複数ある * を置いてキーワード専用にする
渡す個数が呼び出しごとにちがう *args
受け取った設定を別の関数へそのまま渡す **kwargs
引数名を公開したくない、あとで変える可能性がある / を置いて位置専用にする

TypeError(引数の過不足)を再現して直す

引数の渡し方が定義と合わないときは、TypeErrorが発生します。3つの関数に対して、合わない呼び出しをまとめて試しました。

def area(width, height):
    return width * height

def info(name, *, upper=False):
    return name.upper() if upper else name

def point(x, y, /):
    return (x, y)

calls = [
    ("area(3)",                 lambda: area(3)),
    ("area()",                  lambda: area()),
    ("area(3, 4, 5)",           lambda: area(3, 4, 5)),
    ("area(3, 4, depth=5)",     lambda: area(3, 4, depth=5)),
    ("area(3, width=4)",        lambda: area(3, width=4)),
    ("info('abc', True)",       lambda: info("abc", True)),
    ("point(x=1, y=2)",         lambda: point(x=1, y=2)),
    ("area(*[3, 4, 5])",        lambda: area(*[3, 4, 5])),
    ("area('3', '4')",          lambda: area("3", "4")),
]
for label, call in calls:
    try:
        print(f"{label} -> {call()}")
    except TypeError as e:
        print(f"{label}\n    TypeError: {e}")
area(3)
    TypeError: area() missing 1 required positional argument: 'height'
area()
    TypeError: area() missing 2 required positional arguments: 'width' and 'height'
area(3, 4, 5)
    TypeError: area() takes 2 positional arguments but 3 were given
area(3, 4, depth=5)
    TypeError: area() got an unexpected keyword argument 'depth'
area(3, width=4)
    TypeError: area() got multiple values for argument 'width'
info('abc', True)
    TypeError: info() takes 1 positional argument but 2 were given
point(x=1, y=2)
    TypeError: point() got some positional-only arguments passed as keyword arguments: 'x, y'
area(*[3, 4, 5])
    TypeError: area() takes 2 positional arguments but 3 were given
area('3', '4')
    TypeError: can't multiply sequence by non-int of type 'str'
呼び出し メッセージの意味 直し方
area(3) 必須の引数 height が足りない area(3, 4) と渡す。省略可能にしたいなら定義に既定値を付ける
area(3, 4, 5) 2個の定義に3個渡した 余分な引数を外す。個数を自由にしたいなら *args
area(3, 4, depth=5) depth という引数は定義にない 引数名のつづりを確かめる。定義に追加する
area(3, width=4) width に位置(3)と名前(4)の2つの値が来た area(3, height=4) のように別の引数を名前で渡す
info('abc', True) * より後ろの upper を位置で渡した info('abc', upper=True)
point(x=1, y=2) / より前の引数を名前で渡した point(1, 2)
area(*[3, 4, 5]) 展開した結果が3個になった リストの要素数を定義に合わせる
area('3', '4') 個数は合っているが、文字列どうしの掛け算はできない int() などで数値に変換して渡す

メッセージの先頭に関数名、末尾に足りない(または余分な)引数名が書かれているので、そこを読めば原因がわかります。最後の1件は「引数の型」が合わない例で、関数の本体を実行している途中で発生したTypeErrorです。

returnで戻り値を返す

returnなしの関数はNoneを返す

print で表示することと、return で値を返すことは別の動きです。

def show_total(a, b):
    print(a + b)            # 表示するだけ。return がない

def get_total(a, b):
    return a + b            # 値を呼び出し元へ返す

x = show_total(2, 3)
print("show_total の戻り値:", x)

y = get_total(2, 3)
print("get_total の戻り値:", y)
print(get_total(2, 3) * 10)

def only_return():
    return                  # 値なしの return も None

def no_else(n):
    if n > 0:
        return "正の数"
    # n <= 0 のときは return に到達しない

print(only_return(), no_else(5), no_else(-1))

try:
    print(show_total(2, 3) * 10)
except TypeError as e:
    print("TypeError:", e)

nums = [3, 1, 2]
print(nums.sort())          # リストを書き換えるメソッドは None を返す
print(sorted([3, 1, 2]))    # 新しいリストを返す関数
5
show_total の戻り値: None
get_total の戻り値: 5
50
None 正の数 None
5
TypeError: unsupported operand type(s) for *: 'NoneType' and 'int'
None
[1, 2, 3]
関数の書き方 戻り値
return 値 その値
return だけ None
return を書かない None
条件によって return に到達しない 到達しなかったときは None

show_total(2, 3) * 10 は、画面に5を表示したあと、戻り値の None に10を掛けようとしてTypeErrorになっています。「NoneTypeに対して計算できない」というエラーが出たら、関数にreturnがあるかを確かめるのが近道です。計算結果をあとで使う関数は return で返し、表示は呼び出し側で行うと、使い回しやすくなります。

list.sort() のように、対象そのものを書き換えるメソッドがNoneを返すのも同じ考え方です。nums = nums.sort() と書くと nums がNoneになるので、並べ替えた結果を受け取りたいときは sorted() を使います。

returnで処理を早めに終える

return を実行すると、その時点で関数が終わり、以降の行は実行されません。

def shipping_fee(weight_kg: float, total: int) -> str:
    """重さ(kg)と購入額から送料の区分を返す。"""
    if weight_kg <= 0 or total < 0:
        return "入力を確認してください"     # 早めに return して以降を実行しない
    if total >= 5000:
        return "送料無料"
    if weight_kg < 2:
        return "送料 500円"
    return "送料 900円"

print(shipping_fee(1.2, 3000))
print(shipping_fee(3.5, 3000))
print(shipping_fee(3.5, 8000))
print(shipping_fee(0, 3000))

def first_even(numbers):
    for n in numbers:
        if n % 2 == 0:
            return n            # ループの途中でも return で関数ごと終わる
    return None

print(first_even([3, 7, 8, 10]), first_even([1, 3]))
送料 500円
送料 900円
送料無料
入力を確認してください
8 None

当てはまらない場合を先に返しておくと、字下げが深くならず、上から順に読めます。「見つからなかったときはNoneを返す」と決めた関数では、最後に return None を明示しておくと意図が伝わります。

複数の戻り値を返す

複数の戻り値の正体はタプル

return a, b とカンマで並べると、複数の値を返せます。実際には1つのタプルにまとめて返しています。

def min_max(numbers):
    return min(numbers), max(numbers)      # カンマで並べるとタプルになる

result = min_max([3, 9, 1, 7])
print(result, type(result))
print(result[0], result[1])

low, high = min_max([3, 9, 1, 7])          # アンパックで別々の変数に受け取る
print(low, high)

_, high_only = min_max([3, 9, 1, 7])       # 使わない値は _ に受ける
print(high_only)

def stats(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

first, *rest = stats([3, 9, 1, 7])         # 残りをまとめて受け取る
print(first, rest)

try:
    a, b = stats([3, 9, 1, 7])             # 受け取る変数の数が合わない
except ValueError as e:
    print("ValueError:", e)
(1, 9) <class 'tuple'>
1 9
1 9
9
1 [9, 5.0]
ValueError: too many values to unpack (expected 2, got 3)
受け取り方 書き方 結果
まとめて1つの変数に result = min_max(…) タプル (1, 9)
アンパック low, high = min_max(…) それぞれの変数に入る
一部だけ使う _, high = min_max(…) 使わない値は _ に受けるのが慣習
残りをまとめる first, *rest = stats(…) rest はリストになる

左辺の変数の数が戻り値の数と合わないと、ValueError: too many values to unpack(多い場合)や not enough values to unpack(少ない場合)になります。

型ヒント付きで書く・名前付きで返す

from typing import NamedTuple
from dataclasses import dataclass

def divide(a: int, b: int) -> tuple[int, int]:
    """商と余りを返す。"""
    return a // b, a % b

q, r = divide(17, 5)
print(q, r)
print(divide.__annotations__)

class Stats(NamedTuple):
    minimum: int
    maximum: int
    average: float

def stats(numbers: list[int]) -> Stats:
    return Stats(min(numbers), max(numbers), sum(numbers) / len(numbers))

s = stats([3, 9, 1, 7])
print(s)
print(s.maximum, s.average)      # 名前で取り出せる
lo, hi, avg = s                  # タプルなのでアンパックもできる
print(lo, hi, avg)

@dataclass
class Result:
    ok: bool
    message: str = ""

def validate(text: str) -> Result:
    if not text:
        return Result(False, "空です")
    return Result(True)

print(validate(""), validate("abc"))

def as_dict(numbers: list[int]) -> dict[str, int]:
    return {"min": min(numbers), "max": max(numbers)}

print(as_dict([3, 9, 1, 7])["max"])
3 2
{'a': <class 'int'>, 'b': <class 'int'>, 'return': tuple[int, int]}
Stats(minimum=1, maximum=9, average=5.0)
9 5.0
1 9 5.0
Result(ok=False, message='空です') Result(ok=True, message='')
9

複数の戻り値の型は -> tuple[int, int] のように、タプルの中身の型を順に書きます。

返し方 取り出し方 向いている場面
タプル(return a, b) 順番(アンパック) 2〜3個で、意味が順番から明らか
NamedTuple 名前でも順番でも 3個以上、または順番を取りちがえやすい
dataclass 名前 項目に既定値を付けたい、あとから項目が増えそう
辞書 キー 項目が実行時に決まる、JSONにそのまま変換したい

戻り値が3個を超えたら、名前で取り出せる形に切り替えると、呼び出し側が読みやすくなります。

型ヒントで引数と戻り値の型を示す

def greet(name: str, times: int = 1, suffix: str | None = None) -> str:
    text = f"こんにちは、{name}さん" * times
    return text if suffix is None else text + suffix

def log(message: str) -> None:      # 戻り値なしは -> None
    print("[LOG]", message)

print(greet("佐藤"))
print(greet("佐藤", 2, "!"))
log("保存しました")
print(greet.__annotations__)

# 型ヒントは実行時にチェックされない(エディタや型チェッカー向けの情報)
print(greet(123, 1))
こんにちは、佐藤さん
こんにちは、佐藤さんこんにちは、佐藤さん!
[LOG] 保存しました
{'name': <class 'str'>, 'times': <class 'int'>, 'suffix': str | None, 'return': <class 'str'>}
こんにちは、123さん
書きたいこと 書き方
引数の型 name: str
型と既定値の両方 times: int = 1
Noneも受け付ける suffix: str | None = None(3.10以降)
戻り値の型 -> str
戻り値なし -> None
リスト・辞書・タプル list[int]、dict[str, int]、tuple[int, int](3.9以降)

最後の行のとおり、name: str と書いていても、数値の 123 を渡してエラーにはなりません。型ヒントは実行時の検査ではなく、エディタの補完や型チェッカー(mypy、Pyrightなど)が読むための情報です。それでも、関数の入口と出口の型が1行で読めるようになるので、書いておく価値は十分にあります。

関数の中の変数(スコープ)

関数の中で作った変数は、その関数の中だけで有効です(ローカル変数)。

total = 0

def add_local(n):
    total = n           # 関数の中だけの別の変数(ローカル変数)
    return total

add_local(5)
print("外側の total:", total)

def add_broken(n):
    total += n          # 代入があるのでローカル扱い → 読む前に値がない
    return total

try:
    add_broken(5)
except UnboundLocalError as e:
    print("UnboundLocalError:", e)

def add_pure(current, n):   # おすすめ: 引数で受け取り、戻り値で返す
    return current + n

total = add_pure(total, 5)
total = add_pure(total, 7)
print("引数と戻り値で更新:", total)

def append_item(items, item):
    items.append(item)      # リストなど変更できるオブジェクトは、中身の変更が外にも見える

cart = ["りんご"]
append_item(cart, "みかん")
print(cart)
外側の total: 0
UnboundLocalError: cannot access local variable 'total' where it is not associated with a value
引数と戻り値で更新: 12
['りんご', 'みかん']
書き方 起きること
関数の中で外側と同じ名前に代入 別のローカル変数が作られ、外側は変わらない
関数の中で total += n(外側の変数のつもり) UnboundLocalError
引数で受け取り、戻り値で返す 関数の入力と出力がはっきりし、テストしやすい
引数のリストに append 呼び出し元のリストも変わる(同じオブジェクトを指しているため)

global total と宣言すれば外側の変数を書き換えられますが、必要な値は引数で受け取り、結果は戻り値で返す形にしておくと、関数どうしの影響が見通しやすくなります。

関数は値として渡せる

関数名のあとにかっこを付けなければ、関数そのものを変数に入れたり、ほかの関数に渡したりできます。

def double(x):
    return x * 2

f = double                  # かっこを付けないと関数そのもの
print(f(5), f.__name__)
print(list(map(double, [1, 2, 3])))
print(sorted(["banana", "Apple", "cherry"], key=str.lower))

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

print(apply(double, 21))
10 double
[2, 4, 6]
['Apple', 'banana', 'cherry']
42

sorted() の key や map() に渡すときは、double() ではなく double とかっこなしで書きます。

関数を書いたあとのチェックリスト

確認すること 見るポイント
コロンと字下げ def の行末に : があり、本体がスペース4つで字下げされているか
結果を返しているか 計算結果を使う関数に return があるか(print だけになっていないか)
すべての分岐でreturnしているか ifの片側だけ return して、もう片側がNoneになっていないか
既定値にリストや辞書を置いていないか =[] や ={} は =None にして中で作る
引数の順番 既定値なし → 既定値あり の順になっているか
オプションはキーワード専用か TrueやFalseの引数は * の後ろに置いたか
戻り値が多すぎないか 3個を超えるなら NamedTuple や dataclass を検討する
型ヒントとdocstring 入口と出口の型、1行の説明を書いたか

よくある質問

Q. 引数と戻り値は必ず必要ですか? A. どちらもなくてかまいません。引数がなければ def f(): と空のかっこを書き、return を書かなければ戻り値は None になります。

Q. printとreturnはどうちがいますか? A. print は画面に表示するだけで、値を呼び出し元に渡しません。return は値を呼び出し元に返し、変数に代入したり、次の計算に使ったりできます。あとで使う値は return で返します。

Q. 複数の戻り値を返すとき、かっこは必要ですか? A. 必要ありません。return a, b と書けばタプルになります。return (a, b) と書いても同じ結果です。受け取る側は x, y = f() とアンパックします。

Q. 位置専用引数とキーワード専用引数はいつ使いますか? A. TrueやFalseのように位置で渡すと意味が読み取りにくい引数は、* の後ろに置いてキーワード専用にします。引数名を呼び出し側に使わせたくないとき、あとで名前を変える可能性があるときは、/ の前に置いて位置専用にします。

まとめ

  • 関数は def 関数名(引数): で定義し、かっこを付けて呼び出す。定義は呼び出しより前に書く
  • 引数は「位置 → キーワード」の順に渡す。既定値ありの引数は、既定値なしの引数より後ろに定義する
  • / より前は位置専用、* より後ろはキーワード専用。オプションはキーワード専用にすると呼び出しが読みやすい
  • デフォルト値は定義時に1回だけ評価される。リストや辞書は None を既定値にして関数の中で作る
  • returnがない関数は None を返す。計算結果は print ではなく return で返す
  • 複数の戻り値の正体はタプル。a, b = f() でアンパックし、型ヒントは -> tuple[int, int] と書く
  • TypeErrorのメッセージには関数名と引数名が出る。足りないのか、多いのか、名前がちがうのかを読み取って直す

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Python 関数defの書き方|引数・デフォルト値・戻り値と複数の戻り値」

株式会社RJCでは、Pythonの関数定義と引数・戻り値の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?