Convertire una stringa in un oggetto di classe Python?


187

Data una stringa come input dell'utente a una funzione Python, mi piacerebbe estrarne un oggetto di classe se c'è una classe con quel nome nello spazio dei nomi attualmente definito. In sostanza, voglio l'implementazione di una funzione che produrrà questo tipo di risultato:

class Foo:
    pass

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

È possibile tutto questo?

python 

2
Ci sono alcune risposte utili qui, ma ho trovato la risposta a questa domanda particolarmente utile: stackoverflow.com/questions/452969/…
Tom

Risposte:


115

Avvertenza : eval()può essere utilizzato per eseguire codice Python arbitrario. Non si dovrebbe mai usare eval()con stringhe non attendibili. (Vedi Security of Python's eval () su stringhe non attendibili? )

Questo sembra più semplice.

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

30
Beaware la ramificazione dell'utilizzo di eval. È meglio assicurarsi che la stringa che hai passato non provenga dall'utente.
Overcloccato il

18
L'uso di eval () lascia la porta aperta per l'esecuzione di codice arbitrario, per motivi di sicurezza dovrebbe essere evitato.
m.kocikowski,

55
Eval non lascia la porta aperta a nulla. Se hai utenti malvagi e malvagi che potrebbero maliziosamente e malvagiamente passare valori negativi per valutare, possono semplicemente modificare la sorgente di Python. Dal momento che possono semplicemente modificare la sorgente di Python, la porta è, è stata e sarà sempre aperta.
S.Lott

34
A meno che il programma in questione non sia in esecuzione su un server.
Vatsu1,

12
@ s-lott Mi dispiace, ma penso che tu stia dando dei pessimi consigli qui. Considerare: quando il computer richiede una password, l'utente malvagio e malizioso potrebbe anche modificare l'eseguibile o la libreria corrispondente e ignorare questo controllo. Pertanto, si potrebbe obiettare che è inutile aggiungere i controlli della password in primo luogo. O è?
Daniel Sk

148

Questo potrebbe funzionare:

import sys

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

Cosa succederà se la classe non esiste?
Riza,

7
Funzionerà solo per la classe definita nel modulo corrente
luc

1
Soluzione davvero piacevole: qui non stai usando import ma un modulo sys più generico.
Stefano Falsetto,

Questo sarebbe diverso da def str_to_class(str): return vars()[str]?
Arne,

113

Potresti fare qualcosa del tipo:

globals()[class_name]

6
Mi piace meglio della soluzione 'eval' perché (1) è altrettanto facile e (2) non ti lascia aperto all'esecuzione arbitraria di codice.
mjumbewu,

2
ma stai usando globals()... un'altra cosa da evitare
Greg,

5
@Greg Forse, ma globals()è probabilmente molto meno male di eval, e non è affatto peggiore di sys.modules[__name__]AFAIK.
Laurence Gonsalves,

2
Sì, vedo il tuo punto, perché stai solo prendendo da esso non impostando nulla
Greg

Che cosa succede se la variabile viene utilizzata in una classe e più istanze per quella classe sono state fatte contemporaneamente allo stesso tempo. Provocherà problemi, poiché si tratta di una variabile globale.
Alok Kumar Singh

98

Vuoi la classe Baz, che vive nel modulo foo.bar. Con Python 2.7, si desidera utilizzare importlib.import_module(), poiché ciò renderà più semplice la transizione a 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

Con 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

Uso:

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)

Questo gestisce con precisione sia le lezioni di vecchio stile che quelle di nuovo stile.


Non hai bisogno di tipi.TypeType; è solo un alias per il "tipo" incorporato.
Glenn Maynard,

1
Sì, lo so, ma sento che è più chiaro da leggere. Immagino che si riduca solo alle preferenze, però.
Evan Fosmark,

17

Ho visto come django gestisce questo

django.utils.module_loading ha questo

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

Puoi usarlo come import_string("module_path.to.all.the.way.to.your_class")



3

Si, puoi fare questo. Supponendo che le tue classi esistano nello spazio dei nomi globale, qualcosa del genere lo farà:

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
In Python 3, non c'è più ClassType.
Riza,

1
Usa isinstance (x, tipo).
Glenn Maynard,

3

In termini di esecuzione di codice arbitrario o di nomi utente non desiderati, potresti avere un elenco di nomi di funzioni / classi accettabili e se l'input corrisponde a uno nell'elenco, viene valutato.

PS: lo so .... un po 'tardi .... ma è per chiunque altro che si imbatte in questo in futuro.


9
Se hai intenzione di mantenere l'elenco, potresti anche mantenere un comando dal nome alla classe. Quindi non è necessario eval ().
Fasaxc,

3

L'uso di importlib ha funzionato al meglio per me.

import importlib

importlib.import_module('accounting.views') 

Questo utilizza la notazione a punti stringa per il modulo Python che si desidera importare.


3

Se vuoi veramente recuperare le classi che crei con una stringa, dovresti memorizzarle (o formarle correttamente, fare riferimento ) in un dizionario. Dopotutto, ciò consentirà anche di nominare le tue classi ad un livello superiore ed evitare di esporre le classi indesiderate.

Esempio, da un gioco in cui le classi di attori sono definite in Python e si desidera evitare che altre classi generali vengano raggiunte dall'input dell'utente.

Un altro approccio (come nell'esempio seguente) sarebbe quello di creare un'intera nuova classe, che contenga quanto dictsopra. Questo avrebbe:

  • Consentire a più titolari di classi di essere creati per un'organizzazione più semplice (come, uno per le classi di attori e un altro per i tipi di suono);
  • Apportare modifiche sia al titolare che alle classi tenute più facilmente;
  • E puoi usare i metodi di classe per aggiungere classi al dict. (Anche se l'astrazione sotto non è davvero necessaria, è solo per ... "illustrazione" ).

Esempio:

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

Questo mi restituisce:

<__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!

Un altro esperimento divertente da fare con quelli è quello di aggiungere un metodo che sottaceti in ClassHoldermodo da non perdere mai tutte le classi che hai fatto: ^)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.