Python sınıfını dinamik olarak yükleme


167

Python sınıfının bir dizesi verildiğinde, örneğin my_package.my_module.MyClass, onu yüklemenin mümkün olan en iyi yolu nedir?

Başka bir deyişle Class.forName(), Java, Python işlevini eşdeğer arıyorum . Google App Engine'de çalışması gerekiyor.

Tercihen bu, sınıfın FQN'sini dize olarak kabul eden ve sınıfa bir başvuru döndüren bir işlev olacaktır:

my_class = load_class('my_package.my_module.MyClass')
my_instance = my_class()

Değişken için sınıf başvurusu atamak gerekir.
pjesi

1
Bu bir kopya gibi görünüyor: stackoverflow.com/questions/452969/…
cdleary

Haklısın, bir kopya, bulduğun için teşekkürler
pjesi

1
Python'da böyle bir sınıf yüklemeye hiç gerek duymadım. Modülün nerede olduğunu biliyorsunuz, neden modülü yüklemek ve sınıflarını Python'un sizden istediği gibi kullanmak çok daha basit
Adam Spence

22
Bunu yapmak için hiç ihtiyacınız olmadıysa, henüz yeterince ilginç program yazmadınız. Denemeye devam.
John Tyree

Yanıtlar:


194

Python belgelerinde, istediğiniz işlev şunlardır:

def my_import(name):
    components = name.split('.')
    mod = __import__(components[0])
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

Basit __import__olmamanın nedeni, bir paket dizesindeki ilk noktayı geçen herhangi bir şeyin içe aktarılmasının, içe aktardığınız modülün bir niteliğidir. Böylece, böyle bir şey işe yaramaz:

__import__('foo.bar.baz.qux')

Yukarıdaki işlevi şöyle çağırmanız gerekir:

my_import('foo.bar.baz.qux')

Veya örneğiniz durumunda:

klass = my_import('my_package.my_module.my_class')
some_object = klass()

EDIT : Ben bu konuda biraz uzakta. Temel olarak yapmak istediğiniz şey şudur:

from my_package.my_module import my_class

Yukarıdaki işlev yalnızca boş bir listeniz varsa gereklidir . Böylece, uygun çağrı şöyle olur:

mod = __import__('my_package.my_module', fromlist=['my_class'])
klass = getattr(mod, 'my_class')

Ben my_import ('my_package.my_module.my_class') denedim ama modül bir modül değil çünkü mantıklı bulundu my_class hiçbir modül bulundu. My_import
pjesi

Bu garip. İlk noktayı geçen her şeye getattr kullanılır. Hiçbir fark olmamalı.
Jason Baker

Teşekkürler, bunun en iyi yol olduğunu düşünüyorum. Şimdi sadece 'my_pakcage.my_module.my_class' dizesini mod_name, klass_name içine bölmenin en iyi yoluna ihtiyacım var ama sanırım bunu anlayabilirim :)
pjesi

2
içe aktarma python belgeleri (kod üzerinde) importlib kullanmak diyor. Adam Spence
naoko


125

Kendinizinkini döndürmek istemiyorsanız, pydocmodülde tam olarak bunu yapan bir işlev vardır :

from pydoc import locate
my_class = locate('my_package.my_module.MyClass')

Bu yaklaşımın burada listelenen diğerlerine göre avantajı, sadece doğrudan modül içindeki bir nesneyi değil, sağlanan noktalı yolda herhangi bir python nesnesini locatebulabilmesidir . Örneğinmy_package.my_module.MyClass.attr .

Tariflerinin ne olduğunu merak ediyorsanız, işte işlev:

def locate(path, forceload=0):
    """Locate an object by name or dotted path, importing as necessary."""
    parts = [part for part in split(path, '.') if part]
    module, n = None, 0
    while n < len(parts):
        nextmodule = safeimport(join(parts[:n+1], '.'), forceload)
        if nextmodule: module, n = nextmodule, n + 1
        else: break
    if module:
        object = module
    else:
        object = __builtin__
    for part in parts[n:]:
        try:
            object = getattr(object, part)
        except AttributeError:
            return None
    return object

pydoc.safeimportFonksiyona dayanır . İşte bunun için dokümanlar:

"""Import a module; handle errors; return None if the module isn't found.

If the module *is* found but an exception occurs, it's wrapped in an
ErrorDuringImport exception and reraised.  Unlike __import__, if a
package path is specified, the module at the end of the path is returned,
not the package at the beginning.  If the optional 'forceload' argument
is 1, we reload the module from disk (unless it's a dynamic extension)."""

1
Bu cevabı iptal ettim. BTW, pydoc'u sadece bunun için ithal etmenin tuhaf göründüğü gibi safeimport'a sahip olan kod: github.com/python/cpython/blob/…
brianray

Ben de bu cevabı iptal ettim, ilgili tüm cevapların en iyisi bu.
Sunding Wei

Bu da qualname(modül ad alanının en üstünde olmayan nesne) doğru şekilde işleyebiliyor gibi görünüyor .
MisterMiyagi

evet, yapabilirsinlocate('my_package.my_module.MyClass.attr.method.etc')
chadrik

109
import importlib

module = importlib.import_module('my_package.my_module')
my_class = getattr(module, 'MyClass')
my_instance = my_class()

9
modülü dinamik olarak içe aktardıktan sonra modül aracılığıyla sınıfa erişebilirsiniz
Adam Spence

Cevabımı daha kısa olması için düzenledim. Bu, Python'a bir sınıf yüklemenin en iyi yoludur.
Adam Spence

BBB-Benny ve Spence! ;)
dKen

Harika! Hatta 2.7 ve üstü standart kütüphanenin bir parçası.
Apteryx

Bir modüle / sınıfa erişmenin doğru yolu budur. Dokümanlar bunu burada belirtiyor
Noel Evans

29
def import_class(cl):
    d = cl.rfind(".")
    classname = cl[d+1:len(cl)]
    m = __import__(cl[0:d], globals(), locals(), [classname])
    return getattr(m, classname)

5
Bu temiz çözüm! Kullanmayı düşünebilirsiniz:(modulename, classname) = cl.rsplit('.', 2)
vdboor

Harika) Farklı araçlarla putils paketi yaratmıştım, aynı zamanda orada sınıf ithal ediyordum. İsterseniz, bu paketten kullanabilirsiniz.
Stan

4
@vdboor rsplit('.', 1)?
Carles Barrobés


18

Django kullanıyorsanız bunu kullanabilirsiniz. Evet, OP'nin django istemediğinin farkındayım, ama bu soruya bir Django çözümü arayan koştum, bir tane bulamadım ve onu arayan bir sonraki çocuk / gal için buraya koydum.

# It's available for v1.7+
# https://github.com/django/django/blob/stable/1.7.x/django/utils/module_loading.py
from django.utils.module_loading import import_string

Klass = import_string('path.to.module.Klass')
func = import_string('path.to.module.func')
var = import_string('path.to.module.var')

Aşağıdakilere sahip olmayan ., kullanmayan reveya argparsekullanmayan bir şeyi içe aktarmak istiyorsanız unutmayın :

re = __import__('re')

6

Burada bulduğum bir şeyi paylaşmak __import__veimportlib bu sorunu çözmeye çalışırken.

Python 3.7.3 kullanıyorum.

dModüldeki sınıfa girmeye çalıştığımda a.b.c,

mod = __import__('a.b.c')

modDeğişken üst ad bakın a.

Yani derse gitmem d, ben gerek

mod = getattr(mod, 'b') #mod is now module b
mod = getattr(mod, 'c') #mod is now module c
mod = getattr(mod, 'd') #mod is now class d

Yapmaya çalışırsak

mod = __import__('a.b.c')
d = getattr(mod, 'd')

aslında aramaya çalışıyoruz a.d.

Kullanırken importlib, kütüphane getattrbizim için özyinelemeli yaptı galiba . Yani, kullandığımızda importlib.import_module, aslında en derin modül üzerinde bir tutuş elde ederiz.

mod = importlib.import_module('a.b.c') #mod is module c
d = getattr(mod, 'd') #this is a.b.c.d

1

Tamam, benim için bu şekilde çalıştı (Python 2.7 kullanıyorum):

a = __import__('file_to_import', globals(), locals(), ['*'], -1)
b = a.MyClass()

Sonra, b 'Sınıfım' sınıfının bir örneğidir


0

İstediğiniz sınıfın bir örneğine zaten sahipseniz, sınıf türünü ayıklamak için 'type' işlevini kullanabilir ve yeni bir örnek oluşturmak için bunu kullanabilirsiniz:

class Something(object):
    def __init__(self, name):
        self.name = name
    def display(self):
        print(self.name)

one = Something("one")
one.display()
cls = type(one)
two = cls("two")
two.display()


-2

Google App Engine'de webapp2adlı bir işlev var import_string. Daha fazla bilgi için buraya bakın: https://webapp-improved.appspot.com/api/webapp2.html

Yani,

import webapp2
my_class = webapp2.import_string('my_package.my_module.MyClass')

Örneğin bu, webapp2.Routebir işleyici veya dize kullanabileceğiniz yerde kullanılır .

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.