【Python実践自作問題集】No.1 型変数の活用

# 1. はじめに

この問題集は、Pythonの基礎を習得した後、次の段階へ進みたい人のサポートをすることが目的です。
また、競技プログラミングとは異なり、複雑なアルゴリズムの問題ではなく「**可読性の最大化**」に焦点を当てた問題が中心となります。

[目次](https://qiita.com/__Lily__/items/6f70cd6ae0e80593f09d)
前の問題：なし
次の問題：[list/tuple問題の解消](https://qiita.com/__Lily__/items/98b11762b13b2a996ad2)


# 2. 問題

```python:Question
"""
No.1 型変数の活用

次の要件を満たす関数を作成してください。

* list型の引数を1つ取る。リスト内の要素は全て同じ型である。
* 引数のリストが空のとき、Noneを返す。
* 引数のリストが空でないとき、リストの先頭要素を返す。
"""
```

# 3. 解答例

```python:Answer (3.12 <= Python)
def get_first[T](values: list[T]) -> T | None:
    if values:
        return values[0]
    else:
        return None
```

```python:Answer (Python < 3.12)
from typing import TypeVar

T = TypeVar('T')


def get_first(values: list[T]) -> T | None:
    if values:
        return values[0]
    else:
        return None
```

```python:Answer（条件演算式ver）
def get_first[T](values: list[T]) -> T | None:
    return values[0] if values else None
```

# 4. 採点基準

* 関数名が適切であること
* 引数名が適切であること
* TypeHintが適切であること
* 可読性の検討ができていること（PEP8等）


# 5. 解説

## 5.1 関数名

関数名については、今回は`get_first`を採用しました。
先頭要素を取得することから、"first"や"head"のような単語を使用するのが良いでしょう。
注意が必要なのは"get"の方です。
一般的に、関数名で"get"を濫用すべきでないという考えがあります。
これは、"get"（取得する）の意味する範囲が広すぎて、関数名から得られる情報量が少なくなってしまうからです。
データの単純な取得、つまりデータを加工せず、動作も軽量な場合のみ"get"を使うよう意識するのが良いと思います。
今回の場合はこの条件に適合するほか、`values[0]`は`values.__getitem__(0)`の略記であることからも、"get"を使うのは妥当であると判断しました。

[「取得」メソッドの命名では適切な動詞をチョイスしよう - Qiita](https://qiita.com/hockhocklow/items/6f7ab0f5fc794edd49ef)

## 5.2 引数名

引数名については、今回は`values`を採用しました。
一般的に、リストなどの複数の要素が入る型（コンテナ型）の変数名については、複数形にするのが良いです。
そして各要素の変数名を単数形にすることで、リストと要素の区別と対応が付けやすくなります。

```python
values: list[int] = [0, 1, 2]  # リストは複数形
for value in values:  # リストの要素は単数形
    print(value)
```

ここで注意が必要なのは、"data"という単語です。
"data"は不可算名詞であり、"datas"のように書くのは英文法上では誤りです。
歴史を辿れば"datum"という単語の複数形が"data"だったのですが、"datum"は現在では使われなくなっているようです。

[モデルやメソッドに名前を付けるときは英語の品詞に気をつけよう - Qiita](https://qiita.com/jnchito/items/459d58ba652bf4763820)

更に、"data"は意味の範囲が広すぎて情報量がありません。

[クラス、メソッド、変数で勘違いが起こらないような命名にするための方法 - Qiita](https://qiita.com/gts/items/6122e8bf5f7c962ef7f6)

これらの理由から、変数名に"data"を使用するのは避けた方が良いでしょう。
なお"info"/"information"についても同様の理由から、使用を避けた方が良いでしょう。

## 5.3 TypeHint

この問題で一番重要な部分です。
`values`の要素は全て同じ型という条件から、`values`が`list[int]`型であれば`values[0]`は`int`型であり、`values`が`list[str]`型であれば`values[0]`は`str`型になります。
つまり、`values`が`list[T]`型であれば`values[0]`は`T`型というように、異なる変数の間に型の関連が生じます。
この`T`のことを「型変数」と言います。
これをPythonで表現すると次のようになります。

```python
def get_first(values: list[T]) -> T | None:
    # valuesが空のときはNoneを返すので、T | Noneとなる
```

しかし、このままでは`T`が未定義というエラーになってしまいます。
そこで、この`T`が型変数であることを明示します。
記述方法はPythonのバージョンで異なることに注意しましょう。

[Python 3.12で型ヒントがもっと便利に！新機能の紹介と活用例 - Qiita](https://qiita.com/cfdtirej/items/6d7b78233ac2ff3f126e)

[Python3.12からPEP695-Type Parameter Syntax(型引数構文)が導入され、型変数を使ったクラスや関数の定義が大きく変わる - Qiita](https://qiita.com/junkmd/items/1aa7be17401cfebab92d)


```python:3.12 <= Python
def get_first[T](values: list[T]) -> T | None:
    # 関数名直後に角括弧を書くことで、型変数を定義できる
```

```python:Python < 3.12
from typing import TypeVar

T = TypeVar('T')  # typingモジュールのTypeVarを使用する


def get_first(values: list[T]) -> T | None:
```

## 5.4 可読性の検討（PEP8等）

if文でリストが空でないことを判定する場合、方法はいくつかあります。

[[Python] listが空かどうか判定する方法2つ - Qiita](https://qiita.com/yonedaco/items/d0f65ca3dad2e085a51d)

```python
# 方法1. 長さを使った判定
if 0 < len(values):
    ...

# 方法2. 空でないリストがTrueとして扱われることを利用
if values:
    ...
```

基本的に、「空か、空でないか」を判定したい場合は方法2を使うことが推奨されています。
今回の問題では、次のような記述を模範解答とします。

```python
if values:
    return values[0]
else:
    return None
```

ただし、別の場面では方法1の書き方を使うこともあります。
例えば「要素の個数が0か、1か、2以上か」で分岐する場合は、方法1の方が統一感があって可読性が高いこともあります。

```python
if len(values) == 0:
    ...
elif len(values) == 1:
    ...
else:
    ...
```

また、if文を1行で書くこともできます（条件演算式）。

[Pythonの三項演算子（条件演算子）でif文を一行で書く - note.nkmk.me](https://note.nkmk.me/python-if-conditional-expressions/)

```python
return values[0] if values else None
```

今回の問題では条件も処理もシンプルなので、一行で書いても問題ありません。
ただし濫用には注意し、可読性が向上する場合のみの使用に留めましょう。
また、Pythonの条件演算式の歴史について面白い記事があったので紹介します。

[なぜ現代のプログラミング言語は三項演算子を採用しないのか - Qiita](https://qiita.com/h4cker_j/items/c50d93fa88608ef7a871)


# 6. さいごに

Python実践自作問題集の第1問、いかがでしたでしょうか。
単純な関数でも、注意すべきポイントはたくさんあります。
今後も気が向いたときに問題を追加していくのでよろしくお願いします。