Czy Python ma odpowiednik Java Class.forName ()?

95

Muszę wziąć argument w postaci ciągu znaków i utworzyć obiekt klasy nazwanej w tym ciągu w Pythonie. W Javie użyłbym Class.forName().newInstance(). Czy istnieje odpowiednik w Pythonie?


Dzięki za odpowiedzi. Aby odpowiedzieć tym, którzy chcą wiedzieć, co robię: Chcę użyć argumentu wiersza poleceń jako nazwy klasy i utworzyć jej instancję. Właściwie programuję w Jythonie i tworzę instancje klas Java, stąd Java-ność tego pytania. getattr()działa świetnie. Dzięki wielkie.

Jason
źródło
Zobacz stackoverflow.com/a/10675081/116891,importlib.import aby zapoznać się z przykładem użycia , które zostało przeniesione z pythona 3 do 2.7 ( docs.python.org/2/library/importlib.html )
Pat

Odpowiedzi:

169

Odbicie w Pythonie jest dużo łatwiejsze i znacznie bardziej elastyczne niż w Javie.

Polecam przeczytanie tego poradnika

Nie ma bezpośredniej funkcji (o której wiem), która pobiera w pełni kwalifikowaną nazwę klasy i zwraca klasę, jednak masz wszystkie elementy potrzebne do jej zbudowania i możesz je połączyć.

Jedna rada: nie próbuj programować w stylu Java, gdy jesteś w Pythonie.

Jeśli potrafisz wyjaśnić, co próbujesz zrobić, może pomożemy Ci znaleźć bardziej pytoniczny sposób zrobienia tego.

Oto funkcja, która robi to, co chcesz:

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

Możesz użyć wartości zwracanej przez tę funkcję, tak jakby była ona samą klasą.

Oto przykład użycia:

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

Jak to działa?

Używamy __import__do importowania modułu, który zawiera klasę, co wymagało, abyśmy najpierw wyodrębnili nazwę modułu z w pełni kwalifikowanej nazwy. Następnie importujemy moduł:

m = __import__( module )

W tym przypadku mbędzie odnosić się tylko do modułu najwyższego poziomu,

Na przykład, jeśli twoja klasa mieszka w foo.bazmodule, mbędzie to moduł. foo
Możemy łatwo uzyskać odniesienie do foo.bazusinggetattr( m, 'baz' )

Aby dostać się z modułu najwyższego poziomu do klasy, należy rekurencyjnie użyć gettatrna częściach nazwy klasy

Powiedz na przykład, że jeśli nazwa klasy to foo.baz.bar.Model, robimy to:

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

Oto, co dzieje się w tej pętli:

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

Na końcu pętli mbędzie odniesienie do klasy. Oznacza to, że mjest to właściwie klasa sama w sobie, możesz na przykład:

a = m() #instantiate a new instance of the class    
b = m( arg1, arg2 ) # pass arguments to the constructor
hasen
źródło
6
Exec jest niezwykle niebezpieczny. Łatwo jest strzelić sobie w stopę, dynamicznie nadpisując nazwy w lunecie i równie łatwo jest otworzyć lukę w zabezpieczeniach wielkości Rhode Island.
Cody Brocious
1
Powodem, dla którego exec jest tutaj niepewny, jest to, że możesz użyć ';' w nazwie klasy, aby przerwać import. Może to łatwo pozwolić na wykonanie dowolnego kodu. Powodem, dla którego może to uszkodzić twój kod, jest to, że exec nadpisze kolidujące nazwy, powodując błędy i / lub luki w zabezpieczeniach.
Cody Brocious
8
Tak, po to __import__istnieje. Projekty takie jak Django, które wykonują magię ładowania modułów, używają jej przez cały czas. Niezła odpowiedź.
cdleary
5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Chociaż „object.from_name” może być lepszą nazwą. Przykłady: get_class ('decimal.Decimal'), get_class (nazwa_modułu).
jfs
1
Wystarczyło zdefiniować siedmioliniową funkcję. Prawdziwa łatwość.
Pavel Vlasov
27

Zakładając, że zajęcia są w Twoim zakresie:

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

Inaczej:

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

Edycja: Uwaga, nie możesz nadać getattr nazwy takiej jak „foo.bar”. Musisz to podzielić według. i wywołaj getattr () na każdym elemencie od lewej do prawej. To poradzi sobie z tym:

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
źródło
Czy nie powinno to być globals () ['classname']?
orip
Naprawdę masz rację. Zapomniałem, że globals () nie zwraca rzeczywistego zasięgu globalnego, tylko jego mapowanie. Edycja jako taka - dzięki!
Cody Brocious
To nie jest równoważne z Class.forName w Javie, w Javie nie ma żadnych założeń co do tego, co jest importowane, a co nie
napisano
1
attrs = 'foo.bar.baz'.split('.'); fooBar = reduce(getattr, attrs[1:], __import__(attrs[0]))
jfs
1
Lubmodule, _, 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

Stosowanie

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: 
Poklepać
źródło
1
+1 Za użycie do tego celu importlib, ponieważ zapobiega to konieczności ( spowodowanej przez import ) rekurencyjnego wyszukiwania klasy. Prostsza i czystsza niż zaakceptowana odpowiedź (choć gorzej udokumentowana).
sage88
4

Jeszcze inna realizacja.

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)
greyhemp
źródło
if module_nameLBYL jest dość redudant, ponieważ błąd pojawi się, jeśli po prostu usunąć te linie to: ValueError: Empty module name.
bukzor
3

Wygląda na to, że podchodzisz do tego od środka, a nie od początku. Co tak naprawdę próbujesz zrobić? Znalezienie klasy skojarzonej z danym ciągiem znaków jest środkiem do celu.

Jeśli wyjaśnisz swój problem, który może wymagać Twojej własnej mentalnej refaktoryzacji, może pojawić się lepsze rozwiązanie.

Na przykład: Czy próbujesz załadować zapisany obiekt na podstawie jego nazwy typu i zestawu parametrów? Python pisze to unpickling i powinieneś spojrzeć na moduł pikle . I chociaż proces odbierania przebiega dokładnie tak, jak opisujesz, nie musisz martwić się, jak to działa wewnętrznie:

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

źródło
1
to jest bardzo interesujące, ale jest trochę inne niż pytanie
Nick
1

Znajduje się w standardowej bibliotece Pythona jako unittest.TestLoader.loadTestsFromName. Niestety metoda wykonuje dodatkowe czynności związane z testami, ale ten pierwszy ha wygląda na nadający się do ponownego użycia. Edytowałem go, aby usunąć funkcjonalność związaną z testem:

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
źródło
0

Musiałem pobrać obiekty dla wszystkich istniejących klas w my_package. Więc importuję wszystkie niezbędne klasy do my_packageplików __init__.py.

Więc moja struktura katalogów jest następująca:

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

A mój __init__.pywygląd wygląda tak:

from .module1 import ClassA
from .module2 import ClassB

Następnie tworzę taką funkcję:

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

Gdzie module_name = 'my_package'

sprawdź dokument: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Sushant Chaudhary
źródło