Python a-t-il un équivalent à Java Class.forName ()?


95

J'ai le besoin de prendre un argument de chaîne et de créer un objet de la classe nommée dans cette chaîne en Python. En Java, j'utiliserais Class.forName().newInstance(). Existe-t-il un équivalent en Python?


Merci pour les réponses. Pour répondre à ceux qui veulent savoir ce que je fais: je veux utiliser un argument de ligne de commande comme nom de classe et l'instancier. En fait, je programme en Jython et instancie des classes Java, d'où le caractère Java de la question. getattr()fonctionne très bien. Merci beaucoup.


Voir stackoverflow.com/a/10675081/116891 pour un exemple d'utilisation importlib.import, qui a été rétroporté de python 3 à 2.7 ( docs.python.org/2/library/importlib.html )
Pat

Réponses:


169

La réflexion en python est beaucoup plus facile et beaucoup plus flexible qu'en Java.

Je recommande de lire ce tutoriel

Il n'y a pas de fonction directe (que je connaisse) qui prend un nom de classe pleinement qualifié et renvoie la classe, mais vous avez toutes les pièces nécessaires pour construire cela, et vous pouvez les connecter ensemble.

Un petit conseil cependant: n'essayez pas de programmer en style Java lorsque vous êtes en python.

Si vous pouvez expliquer ce que vous essayez de faire, nous pouvons peut-être vous aider à trouver une manière plus pythonique de le faire.

Voici une fonction qui fait ce que vous voulez:

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

Vous pouvez utiliser la valeur de retour de cette fonction comme s'il s'agissait de la classe elle-même.

Voici un exemple d'utilisation:

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

Comment ça marche?

Nous utilisons __import__pour importer le module qui contient la classe, ce qui nécessitait d'extraire d'abord le nom du module du nom complet. Ensuite, nous importons le module:

m = __import__( module )

Dans ce cas, mse référera uniquement au module de niveau supérieur,

Par exemple, si votre classe vit dans le foo.bazmodule, alors msera le module foo
Nous pouvons facilement obtenir une référence à l' foo.bazutilisationgetattr( m, 'baz' )

Pour passer du module de niveau supérieur à la classe, il faut utiliser récursivement gettatrsur les parties du nom de la classe

Disons par exemple, si votre nom de classe est foo.baz.bar.Modelalors nous faisons ceci:

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

C'est ce qui se passe dans cette boucle:

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

À la fin de la boucle, msera une référence à la classe. Cela signifie qu'il ms'agit en fait de la classe itslef, vous pouvez faire par exemple:

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

6
Exec est extrêmement dangereux. Il est facile de se tirer une balle dans le pied en écrasant dynamiquement les noms dans votre lunette et tout aussi facile d'ouvrir une faille de sécurité de la taille de Rhode Island.
Cody Brocious

1
La raison pour laquelle exec n'est pas sûr ici est que vous pouvez utiliser un ';' dans le nom de la classe pour sortir de l'importation. Cela peut facilement permettre l'exécution de code arbitraire. La raison pour laquelle cela peut endommager votre code est que exec écrasera les noms en collision, provoquant des bogues et / ou des failles de sécurité.
Cody Brocious

8
Oui, c'est pour ça qu'il __import__existe. Des projets comme Django qui font du module-loading-magic l'utilisent tout le temps. Bonne réponse.
cdleary

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Bien que «object.from_name» soit un meilleur nom. Exemples: get_class ('decimal.Decimal'), get_class (nom_module).
jfs

1
Il suffit de définir une fonction de sept lignes. Une vraie facilité.
Pavel Vlasov

27

En supposant que la classe est dans votre portée:

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

Autrement:

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

Edit: Notez que vous ne pouvez pas donner un nom tel que «foo.bar» à getattr. Vous devrez le diviser par. et appelez getattr () sur chaque morceau de gauche à droite. Cela permettra de gérer cela:

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

Ne devrait-il pas s'agir de globals () ['classname']?
orip le

Vous avez en effet raison. J'ai oublié que globals () ne renvoie pas la portée globale réelle, juste un mappage de celle-ci. Montage en tant que tel - merci!
Cody Brocious

Cela n'est pas équivalent à Class.forName de java, en Java, aucune hypothèse n'est faite sur ce qui est importé et ce qui ne l'est pas
hasen

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

1
Oumodule, _, 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

Usage

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 Pour utiliser importlib pour faire cela, car cela évite le besoin (que cause l' importation ) de trouver récursivement la classe. Plus simple et plus propre que la réponse acceptée (bien que moins bien documentée).
sage88

4

Encore une autre mise en œuvre.

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)

Le if module_nameLBYL est assez redudant, car l'erreur que vous obtenez si vous supprimez simplement ces lignes est: ValueError: Empty module name.
bukzor

3

Il semble que vous abordez cela du milieu au lieu du début. Qu'essayez-vous vraiment de faire? Trouver la classe associée à une chaîne donnée est un moyen d'arriver à une fin.

Si vous clarifiez votre problème, ce qui pourrait nécessiter votre propre refactorisation mentale, une meilleure solution peut se présenter.

Par exemple: essayez-vous de charger un objet enregistré en fonction de son nom de type et d'un ensemble de paramètres? Python épelle cette suppression et vous devriez regarder le module pickle . Et même si le processus de décapage fait exactement ce que vous décrivez, vous n'avez pas à vous soucier de son fonctionnement en interne:

>>> 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
c'est très intéressant, mais c'est un peu différent de la question
Nick

1

Cela se trouve dans la bibliothèque standard python, sous le nom unittest.TestLoader.loadTestsFromName. Malheureusement, la méthode continue à faire des activités supplémentaires liées aux tests, mais ce premier ha semble réutilisable. Je l'ai modifié pour supprimer la fonctionnalité liée au 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

J'avais besoin d'obtenir des objets pour toutes les classes existantes dans my_package. J'importe donc toutes les classes nécessaires dans my_package's __init__.py.

Donc, ma structure de répertoires est comme ceci:

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

Et mon __init__.pyressemble à ceci:

from .module1 import ClassA
from .module2 import ClassB

Ensuite, je crée une fonction comme celle-ci:

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

module_name = 'my_package'

inspecter doc: https://docs.python.org/3/library/inspect.html#inspect.getmembers

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.