Hat Python ein Äquivalent zu Java Class.forName ()?


95

Ich muss ein String-Argument nehmen und ein Objekt der Klasse erstellen, die in diesem String in Python genannt wird. In Java würde ich verwenden Class.forName().newInstance(). Gibt es ein Äquivalent in Python?


Danke für die Antworten. Um diejenigen zu beantworten, die wissen möchten, was ich tue: Ich möchte ein Befehlszeilenargument als Klassennamen verwenden und es instanziieren. Ich programmiere tatsächlich in Jython und instanziiere Java-Klassen, daher die Java-Ness der Frage. getattr()funktioniert super. Vielen Dank.


Unter stackoverflow.com/a/10675081/116891 finden Sie ein Beispiel für die Verwendung importlib.import, die von Python 3 auf 2.7 ( docs.python.org/2/library/importlib.html ) zurückportiert wurde
Pat

Antworten:


169

Die Reflexion in Python ist viel einfacher und flexibler als in Java.

Ich empfehle dieses Tutorial zu lesen

Es gibt keine direkte Funktion (die ich kenne), die einen vollständig qualifizierten Klassennamen annimmt und die Klasse zurückgibt. Sie haben jedoch alle Teile, die zum Erstellen erforderlich sind, und können sie miteinander verbinden.

Ein Tipp: Versuchen Sie nicht, im Python-Stil zu programmieren, wenn Sie in Python sind.

Wenn Sie erklären können, was Sie versuchen, können wir Ihnen vielleicht helfen, einen pythonischeren Weg zu finden.

Hier ist eine Funktion, die macht, was Sie wollen:

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

Sie können den Rückgabewert dieser Funktion so verwenden, als wäre es die Klasse selbst.

Hier ist ein Anwendungsbeispiel:

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

Wie funktioniert das?

Wir verwenden __import__das Modul, das die Klasse enthält. Daher müssen wir zuerst den Modulnamen aus dem vollständig qualifizierten Namen extrahieren. Dann importieren wir das Modul:

m = __import__( module )

In diesem Fall mbezieht sich nur auf das Modul der obersten Ebene,

Wenn Ihre Klasse beispielsweise im foo.bazModul lebt , ist mdies das Modul. foo
Wir können leicht einen Verweis auf die foo.bazVerwendung erhaltengetattr( m, 'baz' )

Um vom Modul der obersten Ebene zur Klasse zu gelangen, müssen gettatrdie Teile des Klassennamens rekursiv verwendet werden

Sagen foo.baz.bar.Modelwir zum Beispiel, wenn Ihr Klassenname lautet, machen wir Folgendes:

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

Folgendes passiert in dieser Schleife:

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

Am Ende der Schleife mwird ein Verweis auf die Klasse angezeigt. Dies bedeutet, dass mes sich tatsächlich um die Klasse selbst handelt, die Sie beispielsweise ausführen können:

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

6
Exec ist extrem gefährlich. Es ist einfach, sich in den Fuß zu schießen, indem Sie Namen in Ihrem Bereich dynamisch überschreiben und eine Sicherheitslücke in der Größe von Rhode Island öffnen.
Cody Brocious

1
Der Grund, warum exec hier unsicher ist, ist, dass Sie ein ';' im Klassennamen, um aus dem Import auszubrechen. Dies kann leicht die Ausführung von beliebigem Code ermöglichen. Der Grund dafür, dass Ihr Code beschädigt werden kann, besteht darin, dass exec kollidierende Namen überschreibt und Fehler und / oder Sicherheitslücken verursacht.
Cody Brocious

8
Ja, dafür gibt __import__es. Projekte wie Django, die Modul-Lade-Magie betreiben, verwenden sie ständig. Gute Antwort.
cdleary

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Obwohl 'object.from_name' ein besserer Name sein könnte. Beispiele: get_class ('decimal.Decimal'), get_class (Modulname).
JFS

1
Es war nur erforderlich, eine Funktion mit sieben Zeilen zu definieren. Eine echte Leichtigkeit.
Pavel Vlasov

27

Angenommen, die Klasse liegt in Ihrem Geltungsbereich:

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

Andernfalls:

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

Bearbeiten: Beachten Sie, dass Sie getattr keinen Namen wie 'foo.bar' geben können. Sie müssen es durch teilen. und rufe getattr () für jedes Stück von links nach rechts auf. Dies wird das erledigen:

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

Sollte es nicht globals () ['Klassenname'] sein?
orip

Sie sind in der Tat richtig. Ich habe vergessen, dass globals () nicht den tatsächlichen globalen Bereich zurückgibt, sondern nur eine Zuordnung davon. Bearbeitung als solche - danke!
Cody Brocious

Dies ist nicht gleichbedeutend mit Java Class.forName, in Java werden keine Annahmen darüber getroffen, was importiert wird und was nicht
hasen

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

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

Verwendung

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 Für die Verwendung von importlib, da dies verhindert, dass die Klasse rekursiv gefunden werden muss (was der Import verursacht). Einfacher und sauberer als die akzeptierte Antwort (obwohl weniger gut dokumentiert).
sage88

4

Noch eine Implementierung.

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)

Das if module_nameLBYL ist ziemlich redundant, da der Fehler, den Sie erhalten, wenn Sie diese Zeilen einfach löschen, ist : ValueError: Empty module name.
Bukzor

3

Es scheint, dass Sie sich dem von der Mitte anstatt von Anfang an nähern. Was versuchst du wirklich zu tun? Das Finden der Klasse, die einer bestimmten Zeichenfolge zugeordnet ist, ist ein Mittel zum Zweck.

Wenn Sie Ihr Problem klären, was möglicherweise Ihr eigenes mentales Refactoring erfordert, bietet sich möglicherweise eine bessere Lösung an.

Zum Beispiel: Versuchen Sie, ein gespeichertes Objekt basierend auf seinem Typnamen und einer Reihe von Parametern zu laden? Python buchstabiert dieses Unpickling und Sie sollten sich das Pickle-Modul ansehen . Und obwohl der Aufhebungsprozess genau das tut, was Sie beschreiben, müssen Sie sich keine Gedanken darüber machen, wie er intern funktioniert:

>>> 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
Das ist sehr interessant, aber etwas anders als die Frage
Nick

1

Dies befindet sich in der Python-Standardbibliothek als unittest.TestLoader.loadTestsFromName. Leider führt die Methode zusätzliche testbezogene Aktivitäten durch, aber diese erste Version scheint wiederverwendbar zu sein. Ich habe es bearbeitet, um die testbezogene Funktionalität zu entfernen:

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

Ich musste Objekte für alle vorhandenen Klassen in bekommen my_package. Also importiere ich alle notwendigen Klassen in my_package's __init__.py.

Meine Verzeichnisstruktur sieht also so aus:

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

Und mein __init__.pysieht so aus:

from .module1 import ClassA
from .module2 import ClassB

Dann erstelle ich eine Funktion wie folgt:

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

Wo module_name = 'my_package'

Überprüfen Sie das Dokument: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.