Python'un Java Class.forName () ile eşdeğeri var mı?


95

Bir dize argümanı alıp Python'da bu dizede adlandırılmış sınıfın bir nesnesini yaratmam gerekiyor. Java'da kullanırdım Class.forName().newInstance(). Python'da bir eşdeğeri var mı?


Cevaplar için teşekkürler. Ne yaptığımı bilmek isteyenlere cevap vermek için: Sınıf adı olarak bir komut satırı argümanı kullanmak ve onu başlatmak istiyorum. Aslında Jython'da programlama yapıyorum ve Java sınıflarını örneklendiriyorum, bu nedenle sorunun Java-özelliği. getattr()harika çalışıyor. Çok teşekkürler.


Bkz stackoverflow.com/a/10675081/116891 kullanılarak bir örnek için importlib.import2,7 (üzere piton 3'ten backported edildi docs.python.org/2/library/importlib.html )
Pat

Yanıtlar:


169

Python'da yansıtma, Java'dakinden çok daha kolay ve çok daha esnektir.

Bu öğreticiyi okumanızı tavsiye ederim

Tam nitelikli bir sınıf adını alan ve sınıfı döndüren doğrudan bir işlev (bildiğim kadarıyla) yoktur, ancak bunu oluşturmak için gereken tüm parçalara sahipsiniz ve bunları birbirine bağlayabilirsiniz.

Yine de bir parça tavsiye: python'dayken Java tarzında programlamaya çalışmayın.

Yapmaya çalıştığınız şeyin ne olduğunu açıklayabilirseniz, belki bunu yapmanın daha pitonik bir yolunu bulmanıza yardımcı olabiliriz.

İşte istediğinizi yapan bir işlev:

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

Bu işlevin dönüş değerini, sanki sınıfın kendisiymiş gibi kullanabilirsiniz.

İşte bir kullanım örneği:

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

Bu nasıl çalışıyor?

__import__Sınıfı tutan modülü içe aktarmak için kullanıyoruz , bu da öncelikle modül adını tam nitelikli addan çıkarmamızı gerektiriyor. Ardından modülü içe aktarıyoruz:

m = __import__( module )

Bu durumda, myalnızca üst düzey modüle atıfta bulunulacaktır,

Örneğin, sınıf içinde yaşıyorsa foo.bazmodülü, daha sonra mmodül olacak foo
Biz kolayca bir referans elde edebilirsiniz foo.bazkullanarakgetattr( m, 'baz' )

En üst düzey modülden sınıfa geçmek için gettatr, sınıf adının bölümlerinde yinelemeli olarak kullanmanız gerekir

Örneğin, sınıf adınız ise foo.baz.bar.Modelşunu yapalım:

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

Bu döngüde olan şey şu:

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

Döngünün sonunda m, sınıfa bir referans olacaktır. Bu m, aslında itslef sınıfı olduğu anlamına gelir, örneğin şunları yapabilirsiniz:

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

6
Exec son derece tehlikelidir. Dürbününüzdeki isimlerin üzerine dinamik olarak yazarak kendinizi ayağınızdan vurmak ve Rhode Island büyüklüğünde bir güvenlik açığını açmak kadar kolay.
Serafina Brocious

1
Exec burada güvensiz olmasının nedeni bir ';' kullanabilmenizdir. içe aktarma işleminden çıkmak için sınıf adında. Bu, rastgele kod yürütülmesine kolayca izin verebilir. Kodunuza zarar vermesinin nedeni, exec çakışan adların üzerine yazarak hatalara ve / veya güvenlik açıklarına neden olmasıdır.
Serafina Brocious

8
Evet, bunun için __import__var olan budur. Django gibi modül yükleme sihri yapan projeler bunu her zaman kullanır. Güzel cevap.
cdleary

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Yine de 'object.from_name' daha iyi bir isim olabilir. Örnekler: get_class ('ondalık. Ondalık'), get_class (modül_adı).
jfs

1
Yalnızca yedi satırlık bir işlevi tanımlamak yeterliydi. Gerçek bir kolaylık.
Pavel Vlasov

27

Sınıfın kapsamınızda olduğunu varsayarsak:

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

Aksi takdirde:

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

Düzenleme: Not, getattr için 'foo.bar' gibi bir isim veremezsiniz. Bölmeniz gerekecek. ve soldan sağa her parça için getattr () çağırın. Bu bunu halledecektir:

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

Küresel () ['sınıf adı'] olması gerekmez mi?
orip

Gerçekten haklısın. Globals () 'ın gerçek genel kapsamı döndürmediğini unuttum, sadece bir eşlemesi. Bu şekilde düzenleme - teşekkürler!
Serafina Brocious

Bu, java'nın Class.forName'ine eşdeğer değildir, Java'da, neyin içe aktarılıp neyin ithal edilmediğine dair hiçbir varsayımda bulunulmaz
hasen

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

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

16
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

Kullanım

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 Bunu yapmak için importlib'i kullanmak için, çünkü bu , sınıfı yinelemeli olarak bulma ihtiyacını (bu içe aktarmanın neden olduğu) engeller . Kabul edilen cevaptan daha basit ve daha temiz (ancak daha az belgelenmiş).
sage88

4

Yine başka bir uygulama.

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)

if module_nameSadece bu satırları silerseniz Alacağınız hata olduğundan LBYL oldukça redudant geçerli: ValueError: Empty module name.
bukzor

3

Görünüşe göre buna baştan değil ortadan yaklaşıyorsun. Gerçekten ne yapmaya çalışıyorsun Belirli bir dizeyle ilişkili sınıfı bulmak, bir amaç için bir araçtır.

Kendi zihinsel yeniden düzenlemenizi gerektirebilecek probleminizi açıklığa kavuşturursanız, daha iyi bir çözüm ortaya çıkabilir.

Örneğin: Kayıtlı bir nesneyi tür adına ve bir dizi parametreye göre mi yüklemeye çalışıyorsunuz? Python bu çözülmeyi heceler ve turşu modülüne bakmalısınız . Ve çözme süreci tam olarak tanımladığınız şeyi yapsa da, dahili olarak nasıl çalıştığı konusunda endişelenmenize gerek yok:

>>> 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
bu çok ilginç, ancak sorudan biraz farklı
Nick

1

Bu, python standart kitaplığında unittest.TestLoader.loadTestsFromName olarak bulunur. Ne yazık ki yöntem testle ilgili ek faaliyetler yapmaya devam ediyor, ancak bu ilk olarak yeniden kullanılabilir görünüyor. Testle ilgili işlevselliği kaldırmak için düzenledim:

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

İçindeki tüm mevcut sınıflar için nesneleri almam gerekiyordu my_package. Bu yüzden gerekli tüm sınıfları my_package's' e aktarıyorum __init__.py.

Yani benim dizin yapım şöyle:

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

Ve __init__.pyşuna benziyor:

from .module1 import ClassA
from .module2 import ClassB

Sonra şöyle bir işlev oluşturuyorum:

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

Nerede module_name = 'my_package'

belgeyi inceleyin: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.