PythonにおけるString→Bool値の変換 一考

## 事の発端

"false"というString値をFalseというBool値に変換したいけど、良い方法は無いか？

もうちょっと踏み込んで言うと、 C# における [Boolean.Parse](http://msdn.microsoft.com/ja-jp/library/system.boolean.parse%28v=vs.110%29.aspx) のようなものは無いか？ということ。

## 現段階での結論

後述する distutils.util.strtobool() + bool() が落としどころかな。

## 以前の結論1

その前に考えていたのは、これ。

```py
def CBool( value ):
    if isinstance( value, str ) and value.lower() == "false":
        return False
    return bool( value )
```

```py
$ python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def CBool( value ):
...     if isinstance( value, str ) and value.lower() == "false":
...         return False
...     return bool( value )
... 
>>> CBool( "False" )
False
>>> CBool( "false" )
False
>>> CBool( 0 )
False
>>> CBool( [] )
False
>>> CBool( "" )
False
>>> CBool( "True" )
True
>>> CBool( 1 )
True
>>> CBool( [ "hoge" ] )
True
>>> CBool( "hoge" )
True
```

## 試行

### bool() 組み込み関数

これだと、「事の発端」に挙げた問題は解消できない。

Python 2.6の公式ドキュメント [5. 組み込み型 > 5.1. 真理値テスト](http://docs.python.jp/2.6/library/stdtypes.html#truth)によると、次の値に該当する場合がFalse、そうでない場合はTrueとなる。

* ``None``
* ``False``
* 数値型におけるゼロ。例えば ``0``, ``0L``, ``0.0``, ``0j`` 。
* 空のシーケンス型。例えば ``''``, ``()``, ``[]`` 。
* 空のマッピング型。例えば ``{}`` 。
* `__nonzero__` または `__len__` メソッドが定義されているようなユーザ定義クラスのインスタンスで、それらのメソッドが整数値ゼロまたは `bool` 値の ``False`` を返すとき。

```py
$ python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> bool( "False" )
True
```

### json.loads()

「事の発端」は解消できるけど、json.loads()本来の使い方と違うので、他の人が読んだら意図がわかりづらくなりかねない。
また、JSONの値が入る前提なので、全て小文字の"false"ではないと通らない。"False"にするとValueErrorとなる。

```py
$ python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import json
>>> json.loads( "false" )
False
>>> json.loads( "False" )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.6/json/__init__.py", line 307, in loads
    return _default_decoder.decode(s)
  File "/usr/lib64/python2.6/json/decoder.py", line 319, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib64/python2.6/json/decoder.py", line 338, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

```

### [ast](http://docs.python.jp/2/library/ast.html) モジュールを使う

これも「事の発端」は解消できる。また、ast.literal_eval()は、単なるeval()と違い変換はリテラル値に絞られているので、どこの骨かもわからない変数をパースするときにでもリスクが低いのもポイント。ただし、"false"はパースできない。

```py
$ python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> ast.literal_eval( "False" )
False
>>> ast.literal_eval( "false" )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.6/ast.py", line 68, in literal_eval
    return _convert(node_or_string)
  File "/usr/lib64/python2.6/ast.py", line 67, in _convert
    raise ValueError('malformed string')
ValueError: malformed string
```

※ @[everes](https://twitter.com/everes/status/452082277598523392)さんのコメントを参考にしました。ありがとうございます。

### 文字列を評価

これも「事の発端」は解消できる。意図もわかりやすい。大文字・小文字の差も吸収できる。ただ、メソッド化してどこかにまとめておいた方が良さそう。ただし、変数に文字列以外が入ってくるとエラーになってしまう。

```py
$ python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> False if "False".lower() == "false" else True
False
>>> False if "false".lower() == "false" else True
False
>>> False if "True".lower() == "false" else True
True
```

※ @[key3](https://twitter.com/key3/status/452081818318024705)さんのコメントを参考にしました。ありがとうございます。

### [distutils](http://docs.python.jp/2/distutils/apiref.html).util.strtobool を使う

これも「事の発端」は解消できる。意図もわかりやすい。また、True / Falseの意図を持った文字列も良きに計らってくれる。ただ、変換対象ではない文字列が入ると例外が出るので、ちゃんと拾うこと。

```py
$ python
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import distutils.util
>>> distutils.util.strtobool( "False" )
0
>>> distutils.util.strtobool( "false" )
0
>>> bool( distutils.util.strtobool( "False" ) )
False
>>> bool( distutils.util.strtobool( "No" ) )
False
>>> distutils.util.strtobool( "True" )
1
>>> bool( distutils.util.strtobool( "True" ) )
True
>>> bool( distutils.util.strtobool( "" ) )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.6/distutils/util.py", line 424, in strtobool
    raise ValueError, "invalid truth value %r" % (val,)
ValueError: invalid truth value ''
```

※ @[indigo13love](https://twitter.com/indigo13love/status/454132379401977856)さんのコメントを参考にしました。ありがとうございます。

## おわりに

- 他に良い方法があったらコメントへお願いします。