¿Python tiene un equivalente a Java Class.forName ()?

95

Tengo la necesidad de tomar un argumento de cadena y crear un objeto de la clase nombrada en esa cadena en Python. En Java, usaría Class.forName().newInstance(). ¿Existe un equivalente en Python?


Gracias por las respuestas. Para responder a aquellos que quieren saber lo que estoy haciendo: quiero usar un argumento de línea de comando como nombre de clase y crear una instancia. De hecho, estoy programando en Jython y creando instancias de clases de Java, de ahí el carácter Java de la pregunta. getattr()Funciona genial. Muchas gracias.

Jason
fuente
Consulte stackoverflow.com/a/10675081/116891 para ver un ejemplo de uso importlib.import, que se exportó de python 3 a 2.7 ( docs.python.org/2/library/importlib.html )
Pat

Respuestas:

169

La reflexión en Python es mucho más fácil y flexible que en Java.

Recomiendo leer este tutorial

No hay una función directa (que yo sepa) que toma un nombre de clase totalmente calificado y devuelve la clase, sin embargo, tiene todas las piezas necesarias para construir eso y puede conectarlas.

Sin embargo, un pequeño consejo: no intente programar en estilo Java cuando esté en Python.

Si puede explicar qué es lo que está tratando de hacer, tal vez podamos ayudarlo a encontrar una forma más pitónica de hacerlo.

Aquí hay una función que hace lo que quieres:

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

Puede utilizar el valor de retorno de esta función como si fuera la propia clase.

Aquí tienes un ejemplo de uso:

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

¿Cómo funciona?

Estamos usando __import__para importar el módulo que contiene la clase, lo que requería que primero extrajáramos el nombre del módulo del nombre completo. Luego importamos el módulo:

m = __import__( module )

En este caso, msolo hará referencia al módulo de nivel superior,

Por ejemplo, si su clase vive en el foo.bazmódulo, entonces mserá el módulo. foo
Podemos obtener fácilmente una referencia para foo.bazusargetattr( m, 'baz' )

Para pasar del módulo de nivel superior a la clase, debe usar de forma recursiva gettatren las partes del nombre de la clase

Digamos, por ejemplo, si el nombre de su clase es, foo.baz.bar.Modelentonces hacemos esto:

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

Esto es lo que está sucediendo en este bucle:

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

Al final del ciclo, mhabrá una referencia a la clase. Esto significa que en mrealidad es la clase en sí misma, puede hacer, por ejemplo:

a = m() #instantiate a new instance of the class    
b = m( arg1, arg2 ) # pass arguments to the constructor
hasen
fuente
6
Exec es extremadamente peligroso. Es fácil dispararse en el pie sobrescribiendo los nombres en su visor de forma dinámica y es igual de fácil abrir una falla de seguridad del tamaño de Rhode Island.
Cody Brocious
1
La razón por la que el ejecutivo es inseguro aquí es que puede usar un ';' en el nombre de la clase para salir de la importación. Esto puede permitir fácilmente la ejecución de código arbitrario. La razón por la que puede dañar su código es que exec sobrescribirá los nombres en conflicto, provocando errores y / o fallas de seguridad.
Cody Brocious
8
Sí, esto es para lo que __import__existe. Los proyectos como Django que hacen magia de carga de módulos lo usan todo el tiempo. Buena respuesta.
cdleary
5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Aunque 'object.from_name' podría ser un nombre mejor. Ejemplos: get_class ('decimal.Decimal'), get_class (nombre_módulo).
jfs
1
Solo fue necesario definir una función de siete líneas. Una verdadera facilidad.
Pavel Vlasov
27

Suponiendo que la clase está en su alcance:

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

De otra manera:

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

Editar: Tenga en cuenta que no puede dar un nombre como 'foo.bar' a getattr. Tendrá que dividirlo. y llame a getattr () en cada pieza de izquierda a derecha. Esto manejará eso:

module, rest = 'foo.bar.baz'.split('.', 1)
fooBar = reduce(lambda a, b: getattr(a, b), rest.split('.'), globals()[module])
someVar = fooBar(args, to, constructor)
Cody Brocious
fuente
¿No debería ser global () ['classname']?
orip
De hecho, tienes razón. Olvidé que globals () no devuelve el alcance global real, solo un mapeo del mismo. Editando como tal - ¡gracias!
Cody Brocious
Esto no es equivalente a Class.forName de java, en Java, no se hacen suposiciones sobre lo que se importa y lo que no
hasen
1
attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0]))
jfs
1
Omodule, _, 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

Uso

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: 
Palmadita
fuente
1
+1 Por usar importlib para hacer esto, ya que evita la necesidad (que causa la importación ) de encontrar recursivamente la clase. Más simple y más limpio que la respuesta aceptada (aunque menos documentada).
sage88
4

Otra implementación más.

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)
cáñamo gris
fuente
El if module_nameLBYL es bastante redudant, ya que el error que se obtiene si simplemente borrar esas líneas es: ValueError: Empty module name.
bukzor
3

Parece que te estás acercando a esto desde el medio en lugar del principio. ¿Qué estás intentando hacer realmente? Encontrar la clase asociada con una cadena dada es un medio para lograr un fin.

Si aclara su problema, que podría requerir su propia refactorización mental, puede presentarse una mejor solución.

Por ejemplo: ¿Está intentando cargar un objeto guardado en función de su nombre de tipo y un conjunto de parámetros? Python deletrea este desencadenamiento y deberías mirar el módulo pickle . Y aunque el proceso de despegado hace exactamente lo que usted describe, no tiene que preocuparse por cómo funciona 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

fuente
1
esto es muy interesante, pero es un poco diferente a la pregunta
Nick
1

Esto se encuentra en la biblioteca estándar de Python, como unittest.TestLoader.loadTestsFromName. Desafortunadamente, el método continúa realizando actividades adicionales relacionadas con las pruebas, pero esta primera ha parece reutilizable. Lo edité para eliminar la funcionalidad relacionada con la prueba:

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
Bukzor
fuente
0

Necesitaba obtener objetos para todas las clases existentes en my_package. Así que importar todas las clases necesarias en my_package's__init__.py .

Entonces mi estructura de directorio es así:

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

Y mi __init__.pyaspecto es así:

from .module1 import ClassA
from .module2 import ClassB

Luego creo una función como esta:

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

Dónde module_name = 'my_package'

inspeccionar el documento: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Sushant Chaudhary
fuente