Pythonの型判定で isinstance() を使ったら、bool型なのにint型と判定されてハマった話
API GatewayとLambdaでWebAPIを作ったとき、リクエストJSONのバリデーションを行うと思います。 その方法はいくつかあります。
- API Gatewayでやる
- Lambdaコードでやる(自前 or ライブラリ)
たとえば、Pythonであれば、下記のライブラリがあります。
そんなJSONバリデーションですが、Pythonでisinstance()
を使ったときに、bool型なのにint型と判定されるケースに遭遇したので、起こったことやどうすればよかったのかをまとめてみました。
ハマったこと
たとえば
次のようなJSONがあります。TODOリストをイメージしています。
{ "taskId": "test001", "title": "あああ", "message": "てすとてすと", "deadlineTimestamp": 123456789, "isCompleted": False }
このJSONをisinstance()
を使って次のようにバリデーションしていました。(本当は文字数とかも確認してますがここでは省略しています)
def validate1(data): task_id = data['taskId'] title = data['title'] message = data['message'] deadline_timestamp = data['deadlineTimestamp'] is_completed = data['isCompleted'] if not isinstance(task_id, str): return False if not isinstance(title, str): return False if not isinstance(message, str): return False if not isinstance(deadline_timestamp, int): return False if not isinstance(is_completed, bool): return False return True
何が起こったか
上記メソッドのテストを書いているときに気づいたのです。次のJSONがバリデーションOKになることを。
{ "taskId": "test001", "title": "あああ", "message": "てすとてすと", "deadlineTimestamp": True, "isCompleted": False }
簡単に再現してみる
isinstance()
にbool型を与えて、int型か?を確認すると、True
になりました。
>>> isinstance(True, int) True >>> isinstance(False, int) True
ほかは想定通りになります。
>>> isinstance(True, str) False >>> isinstance(123, int) True >>> isinstance(123, str) False
どうしてこうなった
isinstance()
の仕様でした。
Return True if the object argument is an instance of the classinfo argument, or of a (direct, indirect or virtual) subclass thereof.
初めて知ったのですが、PythonのBool型は、Integer(整数)のサブクラスとして実装されています。
Python の Bool 型は整数のサブクラスとして実装されています。
つまり、PythonのBool型は整数のサブクラスのため、isinstance(True, int)
は「int型のサブクラスにBool型があるのでTrue
になる」と理解しました。
では、どうするか
type()を使う
オブジェクトの判定であればisinstance()
が推奨されていますが、今回のようにJSONバリデーションをする場合はちょっと嬉しくないです。
そこでtype()
を使えばと考えました。
引数が1つだけの場合、object の型を返します。
オブジェクトの型の判定には、 isinstance() 組み込み関数を使うことが推奨されます。これはサブクラスを考慮するからです。
実際にやってみた
type()
を使えば、型情報が返ってきます。コレを元にif文の条件式を書けばOKです。
>>> type(True) <class 'bool'> >>> type(False) <class 'bool'> >>> type(123) <class 'int'>
たとえば
冒頭のバリデーション関数をtype()
を使うようにしてみました。
def validate2(data): task_id = data['taskId'] title = data['title'] message = data['message'] deadline_timestamp = data['deadlineTimestamp'] is_completed = data['isCompleted'] if type(task_id) != str: return False if type(title) != str: return False if type(message) != str: return False if type(deadline_timestamp) != int: return False if type(is_completed) != bool: return False return True
さいごに
発見したのは偶然ですが気づけてよかったです。気をつけたいと思いました。