Pythonで値の型が曖昧な時どうするべきか？

ある値の型を100%制御できる保証や証明ができない時がある場合、そんな時どうすればよいだろうか？

型推論などやってくれる便利な書き方があるのだろうか？知っている人がいたら教えて欲しい。dictをstructにコンバートするとかもあるのかないのか。何が良いのだろうか。 **曖昧な型が悩ましい** という気持ちになったので、再度考えてみることにした。

### 例 
以下の例の様な`test`というdictデータがあるとする。
その`test`変数の各値の`key`に`1`があるかチェックをしたい。

```python:test_dict_key_value_type.py
test = {
    0: {'key': [1, 2, 3]},
    1: {'key': None},
    2: {'key': []},
    3: {},
    4: None,
}

# パターン1 isinstanceを使ってタイプをチェックしてgetを使って判定
for k, v in test.iteritems():
    if v is not None and isinstance(v.get('key'), list) and 1 in v.get('key'):
        print k, ':found!'


# パターン2 データを再定義する関数を用意しておいく
def set_default_dict_value_type(test, dict_value_key, dict_value_type):
    for k, v in test.iteritems():
        if v is None or not isinstance(v.get(dict_value_key), dict_value_type):
            test[k] = {
                dict_value_key: dict_value_type()
            }
    return test
# new_testのキーの値は全てdictのなのでgetメソッドのみで使うことができる
for k, v in set_default_dict_value_type(test.copy(), 'key', list).iteritems():
    if 1 in v.get('key'):
        print k, ':found!'


# パターン3 tryで処理して無視する
for k, v in test.iteritems():
    try:
        if 1 in v.get('key'):
            print k, ':found!'
    except:
        pass

````  

実行してみる

```shell:bash
$ python test_dict_key_value_type.py
0 :found!
0 :found!
0 :found!
```

### まとめ
今回のケースは、よくよく考えると **データの定義が曖昧** というが問題と言える。なので **データの型を再定義してあげることが問題の解決** という結論に至った。つまり、型が予定と違う場合はデータが壊れているとみなし、型を再定義しても問題ないということでよいと考えることに。上記のケースで行くと`ケース2`が現段階望ましいやり方になる。

### 課題

* 型が予定と違う場合、データが壊れているとみなすので、 **その壊れているデータをどうすのか？** という問題が残る
* パフォーマンス的にはどうなのか深めてみる必要がある
* 再定義の方法や手法についてのベストプラクティスをもっと深めてみる

### サンプルモジュール
listとdictチェクするサンプルモジュールを作ってみた。
まだ、改善箇所はあるかな？

```python:data_type.py
#!/usr/bin/env python


def set_dict_key_value(dic, dict_value_key, dict_value_type):
    """
    :param dict dic: dict data to test
    :param str dict_value_key: dict value key name
    :param Type dict_value_type: Type Object ex. dict, list...
    :rtype: dict
    :return: redefined dict data

    ex.
    >>> test = {
        0: {'key': [1, 2, 3]},
        1: {'key': None},
        2: {'key': []},
        3: {},
        4: None,
    }
    >>> set_dict_key_value(test, 'key', list)
    {
        0: {'key': [1, 2, 3]},
        1: {'key': []},
        2: {'key': []},
        3: {'key': []},
        4: {'key': []}
    }
    """
    new_dic = dic.copy()
    for k, v in new_dic.iteritems():
        if v is None or not isinstance(v.get(dict_value_key), dict_value_type):
            new_dic[k] = {dict_value_key: dict_value_type()}
    return new_dic


def set_list_value(lst, list_value_type, default_none_value=None):
    """
    :param list lst: list data to test
    :param Type list_value_type: Type Object ex. dict, list,,,
    :param not None clean_list_default_value: Any type of data except for None
    :rtype: list
    :return: redefined list data

    ex.
    >>> a = [None, 1, 'str', False, 1.0, {1: 0}]
    >>> set_list_value(a, dict, {})
    [{}, {}, {}, {}, {}, {1: 0}]
    """
    return map(
        lambda v: v if isinstance(v, list_value_type) else list_value_type(),
        lst if default_none_value is None else
        clean_list_none_value(lst, default_none_value)
    )


def clean_list_none_value(lst, default_none_value):
    """
    Replace None with defalut_none_value

    :param list lst: list data to test
    :param not None default_none_value: Any type of data except for None
    :rtype: list
    :return: cleaned list data
    """
    return map(lambda v: default_none_value if v is None else v, lst)
```