5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PythonAdvent Calendar 2023

Day 9

【Python】「`Sequence[str]`は`str`」論争に決着をつける`useful_types.SequenceNotStr`

Last updated at Posted at 2023-12-08

はじめに

よく、Pythonの関数の引数へ型アノテーションをするとき、tuple[str, ...]でもlist[str]でも受け取れるようにしたいということがあると思います。

そのときに

def foo(a: list[str] | tuple[str]): ...

のようにUnionを使って書くと冗長感があるし、他のシーケンス型を受け入れられなくなるということがあります。

しかしSequence[str]を使ってアノテーションしようとすると、下記issueにあるように、「Sequence[str]str」であるので、ただの文字列型も受け入れられてしまうという欠点があります。

from collections.abc import Sequence

def foo(a: Sequence[str]):
    """`('hoge',)`や`['hoge', 'fuga']`を渡して欲しいが、`'hoge'`のような単なる文字列は渡してほしくない"""
    ...


foo(("",))  # Valid
foo(())  # Valid  # 空tupleは長さ0のT型シーケンスと判断されるため
foo([""])  # Valid
foo([])  # Valid  # 空tupleと同じ理由
foo("")  # Validになってしまう。本来咎めてほしい

その解決策として提案されたのが、useful_types.SequenceNotStrです。

useful_typesについて

typingtypeshedmypyのコアコントリビューターが参画し、「痒いところに手が届く」型ヒントシンボルを提供しているリポジトリです。

SequenceNotStrの使用例

useful_typesをインストールしてimportして、引数にアノテーションするとこのようになります。

from useful_types import SequenceNotStr

def foo(a: SequenceNotStr[str]): ...


foo("")  # Invalid
foo(("",))  # Valid
foo(())  # Valid
foo([""])  # Valid
foo([])  # Valid
foo({""})  # Invalid
foo({"": ""})  # Invalid
foo(iter([""]))  # Invalid

SequenceNotStr[str]と型付けされた変数に、ただの文字列を渡すと静的型チェッカーはエラーを表示します。
しかし文字列のリストやタプルであれば、エラーを表示しません

また、「配列」であっても

  • set[str]のように順番を考慮せずindexがないもの
  • dict[str, Any]のように__getitem__にスライスを受け取れないもの
  • Iterator[str]などのイテレータ(Sequenceの基底クラス)

がアサインされると、静的型チェッカーはエラーを表示します。

ただし、コーダーが直接インスタンス化するイテラブルはたいていlisttupleで、これらは組み込み型(組み込み関数)なので、「キャストができない」ということで困ることはほとんどないでしょう。

実装

なぜこのようなことができるかは、ソースコード上にコメントがされています

# This works because str.__contains__ does not accept object (either in typeshed or at runtime)
class SequenceNotStr(Protocol[_T_co]):
    ...
    def __contains__(self, value: object, /) -> bool: ...
    ...

str.__contains__objectを受け入れられない(typeshedでも実行時でも)ので、これは期待通りの働きをする

まとめ

長年(typingにissueが投稿されたのは2016年7月)問題となっていた「Sequence[str]str」はuseful_types.SequenceNotStrによって一つの解決策がもたらされました。

一方、現在ではtyping.Literalもあるので、渡したい文字列種類が限定されるのであれば、Sequence[Literal[...]]を使うことで、どんな文字列を使うべきかを型アノテーションで伝えることができるので、よりコードをロバストにできると考えています。

5
2
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?