0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python実践自作問題集】No.2 list/tuple問題の解消

Last updated at Posted at 2025-06-07

1. はじめに

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

目次
前の問題:型変数の活用
次の問題:整数?実数?

2. 問題

Question
"""
No.2 list/tuple問題の解消

以下のget_first関数を、次の要件を満たすように改修してください。

* 引数valuesは、list型だけではなくtuple型、str型、listを継承したMyList型など、
    values[0]で値を取得できるような全ての型を受け入れる。
* 関数の機能は変えないこと。
    つまり、valuesの長さが0であればNoneを、1以上であれば先頭要素を返す。
"""


class MyList(list):
    pass


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

3. 解答例

Answer
from collections.abc import Sequence


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

4. 採点基準

  • TypeHintが適切であること

5. 解説

5.1 TypeHint

改修前のget_first関数では、引数valuestuple型を受け取ることができませんでした。

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


get_first(values=(0, 1, 2))  # invalid type

valuesの型を修正し、tuple型などを受け取れるようにするのがこの問題の意図です。
しかし、次のようなTypeHintは望ましくありません。

def get_first[T](values: list[T] | tuple[T] | str | MyList[T]) -> T | None:
    ...

Union型を使った書き方ではTypeHintが際限なく長くなってしまいます。
そこで、別の方法を考えることにしましょう。
まずは要求の内容を観察します。
すると、valuesは次の条件を満たせばよいということがわかります。

  • values[0]のように角括弧アクセスができる
  • len(values)で長さを取得できる

そして、それぞれ内部では次のメソッドが呼び出されています。

  • 角括弧アクセス → __getitem__
  • 長さの取得 → __len__

つまりこの状況では、この2つのメソッドをサポートするクラスならvaluesに指定しても正常に処理できます。
では、valuesの型はどう書くべきでしょうか?
ここで登場するのがcollections.abcモジュールです。
この中で、__getitem__, __len__メソッドをサポートするクラスとしてSequenceが定義されています。

collections.abc --- コンテナの抽象基底クラス - docs.python.org

base.png

Add an UML class diagram to the collections.abc module documentation - github.com

ただし見ての通り継承関係が複雑なため、理解するのには骨が折れます。
そこで今回は、「list/tupleのどちらでもよい場合、TypeHintにSequenceを使う」ということだけ覚えればOKです。

以上の議論から、valuesの型はSequence[T]とするのが適切です。

6. 補足

if文の真理値判定について捕捉します。
次のif文ではvaluesの長さが0以外のときTrueと判定されます。

if values:

実はこの短いコードの裏には、巧妙なトリックが仕掛けられています。
まずは一般的な状況で考えましょう。
次のif文で条件が成立するパターンを正確に挙げてみてください。

if something:  # somethingの型は不明とする

正解は次のいずれかのパターンです。

  • something__bool__メソッドが定義されていて、Trueを返す
  • something__bool__メソッドが定義されていない、かつ__len__メソッドが定義されていて0以外の値を返す
  • something__bool__, __len__メソッドが定義されていない

組み込み型 真理値判定 - docs.python.org

実際に試してみましょう。

  • __bool__メソッドが定義されているかで2パターン
  • __bool__メソッドの戻り値(False, True)で2パターン
  • __len__メソッドが定義されているかで2パターン
  • __len__メソッドの戻り値(0, 1)で2パターン

が考えられます。
(ただし、メソッドが定義されていない場合は戻り値を考えられないので重複が生じます。)

No. __bool__の定義 __bool__の戻り値 __len__の定義 __len__の戻り値
1 なし False なし 0
2 なし False なし 1
3 なし False あり 0
4 なし False あり 1
5 なし True なし 0
6 なし True なし 1
7 なし True あり 0
8 なし True あり 1
9 あり False なし 0
10 あり False なし 1
11 あり False あり 0
12 あり False あり 1
13 あり True なし 0
14 あり True なし 1
15 あり True あり 0
16 あり True あり 1
Pythonコード
class Something1:
    pass


class Something2:
    pass


class Something3:

    def __len__(self) -> int:
        return 0


class Something4:

    def __len__(self) -> int:
        return 1


class Something5:
    pass


class Something6:
    pass


class Something7:

    def __len__(self) -> int:
        return 0


class Something8:

    def __len__(self) -> int:
        return 1


class Something9:

    def __bool__(self) -> bool:
        return False


class Something10:

    def __bool__(self) -> bool:
        return False


class Something11:

    def __bool__(self) -> bool:
        return False

    def __len__(self) -> int:
        return 0


class Something12:

    def __bool__(self) -> bool:
        return False

    def __len__(self) -> int:
        return 1


class Something13:

    def __bool__(self) -> bool:
        return True


class Something14:

    def __bool__(self) -> bool:
        return True


class Something15:

    def __bool__(self) -> bool:
        return True

    def __len__(self) -> int:
        return 0


class Something16:

    def __bool__(self) -> bool:
        return True

    def __len__(self) -> int:
        return 1


somethings = {
    1: Something1(),
    2: Something2(),
    3: Something3(),
    4: Something4(),
    5: Something5(),
    6: Something6(),
    7: Something7(),
    8: Something8(),
    9: Something9(),
    10: Something10(),
    11: Something11(),
    12: Something12(),
    13: Something13(),
    14: Something14(),
    15: Something15(),
    16: Something16(),
}
for number, something in somethings.items():
    if something:
        print(f'something{number}: True')
実行結果
something1: True
something2: True
something4: True
something5: True
something6: True
something8: True
something13: True
something14: True
something15: True
something16: True

それぞれ次のパターンに該当します。

  • something__bool__メソッドが定義されていて、Trueを返す
     → No.13, 14, 15, 16
  • something__bool__メソッドが定義されていない、かつ__len__メソッドが定義されていて0以外の値を返す
     → No.4, 8
  • something__bool__, __len__メソッドが定義されていない
     → No.1, 2, 5, 6

要するに、__bool__の戻り値が最優先、次いで__len__の戻り値が採用され、どちらも定義されていない場合はTrue判定となります。
そのため、次のように「__bool____getitem__をサポートするが、__len__はサポートしない」クラスをvaluesに指定してもエラーは発生しません。

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


class SupportsBoolAndGetItem:

    def __init__(self, values: list) -> None:
        self._values = values

    def __bool__(self) -> bool:
        return bool(self._values)

    def __getitem__(self, index: int):
        return self._values[index]


values = SupportsBoolAndGetItem([0, 1, 2])
assert bool(values) is True
assert values[0] == 0
try:
    _ = len(values)
except TypeError as e:
    print(e)
print(get_first(values=values))  # no error
実行結果
object of type 'SupportsBoolAndGetItem' has no len()
0

このSupportsBoolAndGetItemクラスは__len__メソッドをサポートしていないため、collections.abcモジュールのSequenceクラスの子クラスではありません。
このことから、get_firstvalues引数に指定できる型はSequenceより広いことがわかります。
しかしここまで話を広げると流石に収集つかなくなるので、今回は要件に「valuesの長さが~」と書くことで、values__len__メソッドを持つことを保証しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?