@17ec084 (智剛 平田)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

pythonで動的キャストを安全に実現させるには

解決したいこと

pythonで、文字列にて指定された型へとオブジェクトを型変換する方法を探しています。
例えば
cast(int, 3.14)3になるようにするには、単にcast = lambda tipe, obj: tipe(obj)でいいわけですが、
cast("int", 3.14)で同じことをする方法を探しています。

できればevalを使わない安全なものがいいです。

自分で試したこと

動的な型変換を行う方法はなかなか見つかりませんでした。
そこで、文字列を、その意味通りのtypeオブジェクトに変換する関数を書いてみました。

get_type_from_str
import re
def get_type_from_str(string):
    err = type("InjectionAttackBrocked",(Exception,),{})("実行可能コードの恐れのある文字列をブロックしました")
    if type(string) is not str:
        raise err
    try:
        if re.search(r"[0-9a-zA-z_]+",string).group() != string:
            raise err
    except Exception:
        raise err
    try:
        if type(eval(string)) is type:
            return eval(string)
    except Exception:
        return "None"
print(get_type_from_str("int")) # expecting <class 'int'>  
print(get_type_from_str("list"))# expecting <class 'list'>

cast = lambda tipe_str,obj: get_type_from_str(tipe_str)(obj)
print(cast("int", 3.14)) # expecting 3 (because of casting to int)
print(get_type_from_str("get_type_from_str = False")) # expecting InjectionAttackBrocked being raised
実行結果
<class 'int'>
<class 'list'>
3
---------------------------------------------------------------------------
InjectionAttackBrocked                    Traceback (most recent call last)
<ipython-input-102-3049d4234b65> in <module>
     19 cast = lambda tipe_str,obj: get_type_from_str(tipe_str)(obj)
     20 print(cast("int", 3.14)) # expecting 3
---> 21 print(get_type_from_str("get_type_from_str = False"))
     22 
     23 

<ipython-input-102-3049d4234b65> in get_type_from_str(string)
      8             raise err
      9     except Exception:
---> 10         raise err
     11     try:
     12         if type(eval(string)) is type:

<ipython-input-102-3049d4234b65> in get_type_from_str(string)
      6     try:
      7         if re.search(r"[0-9a-zA-z_]+",string).group() != string:
----> 8             raise err
      9     except Exception:
     10         raise err

InjectionAttackBrocked: 実行可能コードの恐れのある文字列をブロックしました

発生している問題・エラー

この関数は正直安全とは言えないと思います。
引数無しの関数が実行可能だからです。
(evalは「式の評価」しかせず、「文の実行」にはexecが必要なことが有名ですが、関数呼び出しは式なので、evalから呼び出し可能であり、副作用も存在し得ます。)

また、もう少し簡単に動的キャストが実現できるのなら、その方が望ましいと考えています。

0 likes

1Answer

文字列から組み込みの型を得るにはこう書けます。

def get_type_from_str(string):
    if hasattr(__builtins__, string):
        attr = getattr(__builtins__, string)
        if isinstance(attr, type):
            return attr
    return None

より安全にするには変換できる型のホワイトリストを持っておくとよさそうです。

ALLOWED_TYPES = {
    'int': int
}

def get_type_from_str(string):
    if string in ALLOWED_TYPES:
        return ALLOWED_TYPES[string]
    return None
2Like

Comments

  1. @17ec084

    Questioner

    ありがとうございます。
    組み込みの型を得るには`{has|get}attr(__builtins__, *)`というのを使うんですね。
    また辞書によりホワイトリストという手もあるんですね!

Your answer might help someone💌