Python ha un equivalente a Java Class.forName ()?


95

Ho la necessità di prendere un argomento stringa e creare un oggetto della classe denominata in quella stringa in Python. In Java, userei Class.forName().newInstance(). C'è un equivalente in Python?


Grazie per le risposte. Per rispondere a coloro che vogliono sapere cosa sto facendo: voglio utilizzare un argomento della riga di comando come nome della classe e istanziarlo. In realtà sto programmando in Jython e istanziando le classi Java, da qui la natura Java della domanda. getattr()funziona alla grande. Grazie mille


Vedi stackoverflow.com/a/10675081/116891 per un esempio di utilizzo importlib.import, di cui è stato eseguito il backport da python 3 a 2.7 ( docs.python.org/2/library/importlib.html )
Pat

Risposte:


169

La riflessione in Python è molto più semplice e molto più flessibile di quanto non sia in Java.

Consiglio di leggere questo tutorial

Non esiste una funzione diretta (che io sappia) che prenda un nome di classe completo e restituisca la classe, tuttavia hai tutti i pezzi necessari per costruirla e puoi collegarli insieme.

Un consiglio però: non provare a programmare in stile Java quando sei in Python.

Se puoi spiegare cosa stai cercando di fare, forse possiamo aiutarti a trovare un modo più pitonico per farlo.

Ecco una funzione che fa quello che vuoi:

def get_class( kls ):
    parts = kls.split('.')
    module = ".".join(parts[:-1])
    m = __import__( module )
    for comp in parts[1:]:
        m = getattr(m, comp)            
    return m

È possibile utilizzare il valore di ritorno di questa funzione come se fosse la classe stessa.

Ecco un esempio di utilizzo:

>>> D = get_class("datetime.datetime")
>>> D
<type 'datetime.datetime'>
>>> D.now()
datetime.datetime(2009, 1, 17, 2, 15, 58, 883000)
>>> a = D( 2010, 4, 22 )
>>> a
datetime.datetime(2010, 4, 22, 0, 0)
>>> 

Come funziona?

Stiamo utilizzando __import__per importare il modulo che contiene la classe, che richiedeva di estrarre prima il nome del modulo dal nome completo. Quindi importiamo il modulo:

m = __import__( module )

In questo caso, si mfarà riferimento solo al modulo di primo livello,

Ad esempio, se la tua classe vive in foo.bazmodule, allora msarà il modulo foo
che possiamo facilmente ottenere un riferimento foo.bazall'usogetattr( m, 'baz' )

Per passare dal modulo di primo livello alla classe, è necessario utilizzare ricorsivamente gettatrle parti del nome della classe

Ad esempio, se il nome della tua classe è, foo.baz.bar.Modelallora facciamo questo:

m = __import__( "foo.baz.bar" ) #m is package foo
m = getattr( m, "baz" ) #m is package baz
m = getattr( m, "bar" ) #m is module bar
m = getattr( m, "Model" ) #m is class Model

Questo è ciò che sta accadendo in questo ciclo:

for comp in parts[1:]:
    m = getattr(m, comp)    

Alla fine del ciclo, msarà un riferimento alla classe. Ciò significa che in mrealtà è la classe a sé stante, puoi fare ad esempio:

a = m() #instantiate a new instance of the class    
b = m( arg1, arg2 ) # pass arguments to the constructor

6
Exec è estremamente pericoloso. È facile spararsi ai piedi sovrascrivendo dinamicamente i nomi nel mirino e altrettanto facile aprire una falla di sicurezza delle dimensioni di Rhode Island.
Cody Brocious,

1
Il motivo per cui exec non è sicuro qui è che puoi usare un ';' nel nome della classe per interrompere l'importazione. Ciò può facilmente consentire l'esecuzione di codice arbitrario. Il motivo per cui può danneggiare il tuo codice è che exec sovrascriverà i nomi in conflitto, causando bug e / o falle di sicurezza.
Cody Brocious,

8
Sì, questo è ciò __import__per cui esiste. Progetti come Django che eseguono la magia del caricamento dei moduli lo usano sempre. Bella risposta.
cdleary

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Anche se "object.from_name" potrebbe essere un nome migliore. Esempi: get_class ('decimal.Decimal'), get_class (module_name).
jfs

1
È bastato definire una funzione di sette righe. Una vera facilità.
Pavel Vlasov

27

Supponendo che la classe sia nel tuo ambito:

globals()['classname'](args, to, constructor)

Altrimenti:

getattr(someModule, 'classname')(args, to, constructor)

Modifica: Nota, non puoi dare un nome come "foo.bar" a getattr. Dovrai dividerlo per. e chiama getattr () su ogni pezzo da sinistra a destra. Questo gestirà che:

module, rest = 'foo.bar.baz'.split('.', 1)
fooBar = reduce(lambda a, b: getattr(a, b), rest.split('.'), globals()[module])
someVar = fooBar(args, to, constructor)

Non dovrebbe essere globals () ['classname']?
orip

Hai davvero ragione. Ho dimenticato che globals () non restituisce l'ambito globale effettivo, ma solo una sua mappatura. Modifica come tale - grazie!
Cody Brocious,

Questo non è equivalente a Class.forName di java, in Java, non vengono fatte supposizioni su cosa sia importato e cosa non lo sia
hasen

1
attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0]))
jfs

1
Oppuremodule, _, attrs = 'foo.bar.baz'.partition('.'); fooBar = reduce(getattr, attrs, __import__(module))
jfs

15
def import_class_from_string(path):
    from importlib import import_module
    module_path, _, class_name = path.rpartition('.')
    mod = import_module(module_path)
    klass = getattr(mod, class_name)
    return klass

Utilizzo

In [59]: raise import_class_from_string('google.appengine.runtime.apiproxy_errors.DeadlineExceededError')()
---------------------------------------------------------------------------
DeadlineExceededError                     Traceback (most recent call last)
<ipython-input-59-b4e59d809b2f> in <module>()
----> 1 raise import_class_from_string('google.appengine.runtime.apiproxy_errors.DeadlineExceededError')()

DeadlineExceededError: 

1
+1 Per usare importlib per fare ciò in quanto impedisce la necessità (che causa l' importazione ) di trovare ricorsivamente la classe. Più semplice e pulita della risposta accettata (anche se meno ben documentata).
sage88

4

Ancora un'altra implementazione.

def import_class(class_string):
    """Returns class object specified by a string.

    Args:
        class_string: The string representing a class.

    Raises:
        ValueError if module part of the class is not specified.
    """
    module_name, _, class_name = class_string.rpartition('.')
    if module_name == '':
        raise ValueError('Class name must contain module part.')
    return getattr(
        __import__(module_name, globals(), locals(), [class_name], -1),
        class_name)

Il if module_nameLBYL è abbastanza redudant, dal momento che l'errore che si ottiene se semplicemente eliminare quelle linee è: ValueError: Empty module name.
bukzor

3

Sembra che tu ti stia avvicinando a questo dal centro invece che dall'inizio. Cosa stai veramente cercando di fare? Trovare la classe associata a una determinata stringa è un mezzo per raggiungere un fine.

Se chiarisci il tuo problema, che potrebbe richiedere il tuo refactoring mentale, potrebbe presentarsi una soluzione migliore.

Ad esempio: stai cercando di caricare un oggetto salvato in base al nome del tipo e a una serie di parametri? Python scrive questo unpickling e dovresti guardare il modulo pickle . E anche se il processo di deselezione fa esattamente quello che descrivi, non devi preoccuparti di come funziona internamente:

>>> class A(object):
...   def __init__(self, v):
...     self.v = v
...   def __reduce__(self):
...     return (self.__class__, (self.v,))
>>> a = A("example")
>>> import pickle
>>> b = pickle.loads(pickle.dumps(a))
>>> a.v, b.v
('example', 'example')
>>> a is b
False

1
questo è molto interessante, ma è leggermente diverso dalla domanda
Nick l'

1

Questo si trova nella libreria standard di Python, come unittest.TestLoader.loadTestsFromName. Sfortunatamente il metodo prosegue con ulteriori attività relative ai test, ma questa prima sembra riutilizzabile. L'ho modificato per rimuovere la funzionalità relativa al test:

def get_object(name):
    """Retrieve a python object, given its dotted.name."""
    parts = name.split('.')
    parts_copy = parts[:]
    while parts_copy:
        try:
            module = __import__('.'.join(parts_copy))
            break
        except ImportError:
            del parts_copy[-1]
            if not parts_copy: raise
    parts = parts[1:]

    obj = module
    for part in parts:
        parent, obj = obj, getattr(obj, part)

    return obj

0

Avevo bisogno di ottenere oggetti per tutte le classi esistenti in my_package. Quindi importa tutte le classi necessarie in my_package's__init__.py .

Quindi la mia struttura di directory è così:

/my_package
    - __init__.py
    - module1.py
    - module2.py
    - module3.py

E il mio __init__.pyaspetto è così:

from .module1 import ClassA
from .module2 import ClassB

Quindi creo una funzione come questa:

def get_classes_from_module_name(module_name):
    return [_cls() for _, _cls in inspect.getmembers(__import__(module_name), inspect.isclass)]

Dove module_name = 'my_package'

ispezionare il documento: https://docs.python.org/3/library/inspect.html#inspect.getmembers

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.