Python関数へのユーザー入力として文字列が与えられた場合、現在定義されている名前空間にその名前のクラスがある場合、そこからクラスオブジェクトを取得したいと思います。基本的に、私はこの種の結果を生成する関数の実装を望んでいます:
class Foo:
pass
str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>
これは可能ですか?
Python関数へのユーザー入力として文字列が与えられた場合、現在定義されている名前空間にその名前のクラスがある場合、そこからクラスオブジェクトを取得したいと思います。基本的に、私はこの種の結果を生成する関数の実装を望んでいます:
class Foo:
pass
str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>
これは可能ですか?
回答:
警告:
eval()任意のPythonコードを実行するために使用できます。あなたは必要があります決して使用しないでeval()信頼されていない文字列で。(信頼されていない文字列に対するPythonのeval()のセキュリティを参照してください?)
これは最も単純なようです。
>>> class Foo(object):
... pass
...
>>> eval("Foo")
<class '__main__.Foo'>
これはうまくいくかもしれません:
import sys
def str_to_class(classname):
return getattr(sys.modules[__name__], classname)
def str_to_class(str): return vars()[str]ますか?
あなたは次のようなことをすることができます:
globals()[class_name]
globals()ます...避けるべきもう一つのこと
globals()おそらく間違いなくに比べてはるかに悪くはなくeval、実際にはsys.modules[__name__]AFAIK よりも悪くはありません。
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')
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)
これは、古いスタイルのクラスと新しいスタイルのクラスの両方を正確に処理します。
私は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")
はい、できます。クラスがグローバル名前空間に存在すると仮定すると、次のようなものがそれを行います:
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つと一致する場合、それは評価されます。
PS:わかった....ちょっと遅れて....しかし、これは将来これに遭遇した誰かのためのものだ。
文字列を使用して作成したクラスを本当に取得したい場合は、参照(または適切に表現された参照)を保存する必要がありますは、それらを辞書に)する必要があります。結局のところ、これにより、クラスの上位レベルに名前を付け、不要なクラスを公開することを回避できます。
例:アクタークラスがPythonで定義されており、ユーザー入力によって他の一般的なクラスに到達しないようにするゲームから。
別のアプローチ(以下の例のような)は、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ことです。そうすることで、行ったすべてのクラスを失うことはありません:^)