- Python
Python 関数defの書き方|引数・デフォルト値・戻り値と複数の戻り値
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期連続の黒字決算。