문자열을 파이썬 클래스 객체로 변환 하시겠습니까?


187

파이썬 함수에 대한 사용자 입력으로 문자열이 주어지면 현재 정의 된 네임 스페이스에 해당 이름의 클래스가있는 경우 클래스 객체를 가져 오려고합니다. 본질적으로 이러한 종류의 결과를 생성하는 함수에 대한 구현을 원합니다.

class Foo:
    pass

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

이것이 가능합니까?

python 

2
여기에는 유용한 답변이 있지만이 질문에 대한 답변이 특히 유용하다는 것을 알았습니다. stackoverflow.com/questions/452969/…
Tom

답변:


115

경고 : eval()임의의 파이썬 코드를 실행하는 데 사용할 수 있습니다. 신뢰할 수없는 문자열과 함께 사용 해서는 안됩니다eval() . ( 신뢰할 수없는 문자열에 대한 Python eval ()의 보안? )

가장 간단한 것 같습니다.

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

30
eval 사용의 결과를 조심하십시오. 전달한 문자열이 사용자가 아닌지 확인하는 것이 좋습니다.
Overclocked

18
eval ()을 사용하면 임의의 코드 실행을 위해 문을 열어두고 보안상의 이유로 피해야합니다.
m.kocikowski 2009 년

54
평가는 문을 열어 두지 않습니다. 악의적이고 악의적 인 사용자가 악의적이고 악의적 인 값을 eval에 전달할 수있는 악의적 인 악의적 인 사용자가있는 경우, 파이썬 소스 만 편집하면됩니다. 파이썬 소스 만 편집 할 수 있기 때문에 문은 열려 있었고 항상 열려 있습니다.
S.Lott

34
해당 프로그램이 서버에서 실행되지 않는 한
Vatsu1

12
@ s-lott 죄송하지만 여기에 매우 나쁜 조언이 있다고 생각합니다. 고려 : 컴퓨터가 암호를 묻는 메시지를 표시하면 악의적 인 악의적 인 사용자가 해당 실행 파일이나 라이브러리를 수정하고이 검사를 무시할 수 있습니다. 따라서 처음에는 암호 검사를 추가하는 것이 의미가 없다고 주장 할 수 있습니다. 아니면?
Daniel Sk

148

이것은 작동 할 수 있습니다 :

import sys

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

수업이 없으면 어떻게 되나요?
riza

7
현재 모듈에 정의 된 클래스에서만 작동합니다
luc

1
정말 좋은 해결책 : 여기에서는 가져 오기를 사용하지 않고 보다 일반적인 sys 모듈을 사용하고 있습니다.
Stefano Falsetto

이것과 다를까요 def str_to_class(str): return vars()[str]?
Arne

113

당신은 다음과 같은 것을 할 수 있습니다 :

globals()[class_name]

6
나는 'eval'솔루션보다 이것을 좋아합니다.
mjumbewu

2
그러나 당신은 globals()피해야 할 또 다른 것을 사용하고 있습니다
Greg

5
@Greg 아마도 아마도 AFAIK globals()보다 훨씬 나쁘지는 eval않지만 실제로 sys.modules[__name__]AFAIK 보다 나쁘지는 않습니다 .
Laurence Gonsalves

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

파이썬 <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)

이것은 구식과 신식 수업을 정확하게 처리합니다.


유형이 필요하지 않습니다. 내장 된 "type"의 별칭 일뿐입니다.
Glenn Maynard

1
예, 알고 있지만 읽기가 더 명확하다고 생각합니다. 그래도 그것은 선호도에 달려 있다고 생각합니다.
Evan Fosmark

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")


이것은 좋은 대답입니다. 다음 은 현재 구현 된 최신 Django 문서에 대한 링크 입니다.
그렉 칼레 카

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이 없습니다.
riza

1
isinstance (x, type)를 사용하십시오.
Glenn Maynard

3

임의 코드 실행 또는 원하지 않는 사용자 전달 이름의 관점에서 허용 가능한 함수 / 클래스 이름 목록이있을 수 있으며 입력이 목록의 이름과 일치하면 평가됩니다.

추신 : 나는 알고 있습니다 .... 킨다 늦었지만 .... 앞으로이 문제를 우연히 발견하는 다른 사람들을위한 것입니다.


9
목록을 유지하려면 이름에서 클래스로의 dict를 유지하십시오. 그러면 eval ()이 필요 없습니다.
Fasaxc

3

importlib을 사용 하면 가장 효과적이었습니다.

import importlib

importlib.import_module('accounting.views') 

가져 오려는 파이썬 모듈에 문자열 점 표기법 을 사용 합니다 .


3

문자열로 만든 클래스를 실제로 검색하려면 사전에 클래스를 저장하거나 올바르게 참조 해야합니다. 결국 클래스의 이름을 더 높이고 원하지 않는 클래스를 노출시키지 않아도됩니다.

예를 들어, 파이썬에서 액터 클래스가 정의되어 있고 사용자 입력으로 다른 일반 클래스에 도달하지 않으려는 게임에서.

다른 예 (아래 예와 같이)는 위의 내용을 포함하는 완전히 새로운 클래스를 만드는 것 dict입니다. 이것은 :

  • 더 쉬운 구성을 위해 여러 클래스 홀더를 만들 수 있습니다 (예 : 배우 클래스 및 사운드 유형).
  • 소지자와 수업을 더 쉽게 수정하십시오.
  • 그리고 클래스 메소드를 사용하여 클래스를 dict에 추가 할 수 있습니다. (아래의 추상화가 실제로 필요하지는 않지만 단지 "illustration" 만을위한 것입니다 ).

예:

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!

그와 관련된 또 다른 재미있는 실험은 피클 링 방법을 추가하여 수행 한 ClassHolder모든 수업을 잃지 않도록하는 것입니다 : ^)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.