文字列をPythonクラスオブジェクトに変換しますか?


187

Python関数へのユーザー入力として文字列が与えられた場合、現在定義されている名前空間にその名前のクラスがある場合、そこからクラスオブジェクトを取得したいと思います。基本的に、私はこの種の結果を生成する関数の実装を望んでいます:

class Foo:
    pass

str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>

これは可能ですか?

python 

2
そこにいくつかの便利な答えがここにありますが、私はこの質問に対する答えは、特に有用であることが分かっ:stackoverflow.com/questions/452969/...
トム・

回答:


115

警告eval()任意のPythonコードを実行するために使用できます。あなたは必要があります決して使用しないでeval()信頼されていない文字列で。(信頼されていない文字列に対するPythonのeval()のセキュリティを参照してください

これは最も単純なようです。

>>> class Foo(object):
...     pass
... 
>>> eval("Foo")
<class '__main__.Foo'>

30
evalの使用による影響に注意してください。渡した文字列がユーザーのものではないことを確認してください。
2013

18
eval()を使用すると、セキュリティ上の理由から回避する必要があるため、任意のコードを実行できるようになります。
m.kocikowski 2013

54
Evalはドアを開いたままにしません。悪意を持って悪質に悪い値をevalに渡す可能性のある悪意のある悪意のあるユーザーがいる場合、Pythonソースを編集するだけで済みます。彼らはpythonソースを編集できるだけなので、ドアは、今も、今も、そしていつでも開かれています。
S.Lott

34
問題のプログラムがサーバーで実行されていない限り。
Vatsu1 2013年

12
@ s-lott申し訳ありませんが、あなたはここで非常に悪いアドバイスをしていると思います。考慮事項:コンピューターからパスワードの入力を求められたら、悪意のある悪意のあるユーザーが対応する実行可能ファイルまたはライブラリを変更し、このチェックをバイパスする可能性があります。したがって、そもそもパスワードチェックを追加しても意味がないと主張する人もいるかもしれません。またはそれは?
Daniel Sk、2015年

148

これはうまくいくかもしれません:

import sys

def str_to_class(classname):
    return getattr(sys.modules[__name__], classname)

クラスが存在しない場合はどうなりますか?
リザ

7
現在のモジュールで定義されているクラスに対してのみ機能します
luc

1
本当に良い解決策:ここではインポートではなく、より一般的なsysモジュールを使用してい ます。
Stefano Falsetto

これは何か違いdef str_to_class(str): return vars()[str]ますか?
Arne

113

あなたは次のようなことをすることができます:

globals()[class_name]

6
(eval)ソリューションよりも簡単で、(2)任意のコード実行の余地がないため、「eval」ソリューションよりも優れています。
mjumbewu

2
しかし、あなたは使用していglobals()ます...避けるべきもう一つのこと
グレッグ

5
@Gregたぶん、globals()おそらく間違いなくに比べてはるかに悪くはなくeval、実際にはsys.modules[__name__]AFAIK よりも悪くはありません。
ローレンスゴンサルベス2013

2
ええ、あなたはそれをつかんでいるだけで何も設定していないので、あなたの要点がわかります
グレッグ

変数がクラスで使用され、そのクラスの複数のインスタンスが同時に作成された場合はどうなるでしょうか。これはグローバル変数であるため、問題が発生しますか?
Alok Kumar Singh

98

Bazモジュールにあるクラスが必要ですfoo.bar。Python 2.7では、を使用する必要がありますimportlib.import_module()。これにより、Python 3への移行が容易になります。

import importlib

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = importlib.import_module(module_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

Python <2.7の場合:

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = __import__(module_name, globals(), locals(), class_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

使用する:

loaded_class = class_for_name('foo.bar', 'Baz')

19
import sys
import types

def str_to_class(field):
    try:
        identifier = getattr(sys.modules[__name__], field)
    except AttributeError:
        raise NameError("%s doesn't exist." % field)
    if isinstance(identifier, (types.ClassType, types.TypeType)):
        return identifier
    raise TypeError("%s is not a class." % field)

これは、古いスタイルのクラスと新しいスタイルのクラスの両方を正確に処理します。


types.TypeType;は必要ありません。これは、組み込みの「タイプ」の単なるエイリアスです。
グレンメイナード

1
ええ、知っていますが、読んだほうがわかりやすいと思います。でもそれは好みに帰着するだけだと思います。
エヴァンフォスマルク2009

17

私はdjangoがこれをどのように処理するかを見てきました

django.utils.module_loadingにはこれがあります

def import_string(dotted_path):
    """
    Import a dotted module path and return the attribute/class designated by the
    last name in the path. Raise ImportError if the import failed.
    """
    try:
        module_path, class_name = dotted_path.rsplit('.', 1)
    except ValueError:
        msg = "%s doesn't look like a module path" % dotted_path
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

    module = import_module(module_path)

    try:
        return getattr(module, class_name)
    except AttributeError:
        msg = 'Module "%s" does not define a "%s" attribute/class' % (
            module_path, class_name)
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

こんな風に使えます import_string("module_path.to.all.the.way.to.your_class")



3

はい、できます。クラスがグローバル名前空間に存在すると仮定すると、次のようなものがそれを行います:

import types

class Foo:
    pass

def str_to_class(s):
    if s in globals() and isinstance(globals()[s], types.ClassType):
            return globals()[s]
    return None

str_to_class('Foo')

==> <class __main__.Foo at 0x340808cc>

1
Python 3では、ClassTypeはなくなりました。
リザ

1
isinstance(x、type)を使用します。
グレンメイナード

3

任意のコード実行、またはユーザーが渡した不要な名前の観点から、受け入れ可能な関数/クラス名のリストがあり、入力がリスト内の1つと一致する場合、それは評価されます。

PS:わかった....ちょっと遅れて....しかし、これは将来これに遭遇した誰かのためのものだ。


9
リストを維持する場合は、代わりに名前からクラスへの辞書を維持することもできます。その場合、eval()は必要ありません。
Fasaxc 2013

3

importlibを使用するのが最も効果的でした。

import importlib

importlib.import_module('accounting.views') 

これは、インポートするPythonモジュールの文字列ドット表記を使用します


3

文字列を使用して作成したクラスを本当に取得したい場合は、参照(または適切に表現された参照)を保存する必要がありますは、それらを辞書に)する必要があります。結局のところ、これにより、クラスの上位レベルに名前を付け、不要なクラスを公開することを回避できます。

例:アクタークラスがPythonで定義されており、ユーザー入力によって他の一般的なクラスに到達しないようにするゲームから。

別のアプローチ(以下の例のような)は、dict上記を保持するまったく新しいクラスを作成することです。これは:

  • 複数のクラスホルダーを作成して整理を容易にする(1つはアクタークラス用、もう1つはサウンドタイプ用など)。
  • 保持者と保持されているクラスの両方に変更を加えます。
  • また、クラスメソッドを使用して、dictにクラスを追加できます。(以下の抽象化は必ずしも必要ではありませんが、単に「イラスト」のためだけです)。

例:

class ClassHolder(object):
    def __init__(self):
        self.classes = {}

    def add_class(self, c):
        self.classes[c.__name__] = c

    def __getitem__(self, n):
        return self.classes[n]

class Foo(object):
    def __init__(self):
        self.a = 0

    def bar(self):
        return self.a + 1

class Spam(Foo):
    def __init__(self):
        self.a = 2

    def bar(self):
        return self.a + 4

class SomethingDifferent(object):
    def __init__(self):
        self.a = "Hello"

    def add_world(self):
        self.a += " World"

    def add_word(self, w):
        self.a += " " + w

    def finish(self):
        self.a += "!"
        return self.a

aclasses = ClassHolder()
dclasses = ClassHolder()
aclasses.add_class(Foo)
aclasses.add_class(Spam)
dclasses.add_class(SomethingDifferent)

print aclasses
print dclasses

print "======="
print "o"
print aclasses["Foo"]
print aclasses["Spam"]
print "o"
print dclasses["SomethingDifferent"]

print "======="
g = dclasses["SomethingDifferent"]()
g.add_world()
print g.finish()

print "======="
s = []
s.append(aclasses["Foo"]())
s.append(aclasses["Spam"]())

for a in s:
    print a.a
    print a.bar()
    print "--"

print "Done experiment!"

これは私を返します:

<__main__.ClassHolder object at 0x02D9EEF0>
<__main__.ClassHolder object at 0x02D9EF30>
=======
o
<class '__main__.Foo'>
<class '__main__.Spam'>
o
<class '__main__.SomethingDifferent'>
=======
Hello World!
=======
0
1
--
2
6
--
Done experiment!

これらを使って行うもう1つの楽しい実験は、pickleするメソッドを追加するClassHolderことです。そうすることで、行ったすべてのクラスを失うことはありません:^)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.