วิธีโหลดคลาส Python แบบไดนามิก


167

รับสตริงของคลาส Python เช่นmy_package.my_module.MyClassวิธีที่ดีที่สุดในการโหลดมันคืออะไร?

ในคำอื่น ๆ ฉันกำลังมองหาเทียบเท่าClass.forName()ใน Java ฟังก์ชั่นใน Python มันต้องทำงานกับ Google App Engine

สิ่งนี้จะเป็นฟังก์ชั่นที่ยอมรับ FQN ของคลาสเป็นสตริงและส่งคืนการอ้างอิงไปยังคลาส:

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

ฉันต้องสามารถกำหนดคลาสอ้างอิงให้กับตัวแปรได้เช่นกัน
pjesi

1
สิ่งนี้ดูเหมือนจะซ้ำซ้อนกับ: stackoverflow.com/questions/452969/…
cdleary

คุณมีสิทธิที่จะเป็นซ้ำขอบคุณสำหรับการหามัน
pjesi

1
ฉันไม่เคยต้องการโหลดคลาสเช่นนี้ใน Python คุณรู้ว่าโมดูลอยู่ที่ไหนทำไมไม่เพียงแค่โหลดโมดูลจากนั้นใช้คลาสเหมือน Python ต้องการให้คุณมันง่ายกว่ามาก
Adam Spence

22
หากคุณไม่จำเป็นต้องทำสิ่งนี้แสดงว่าคุณยังไม่ได้เขียนโปรแกรมที่น่าสนใจ ฝึกต่อไป.
John Tyree

คำตอบ:


194

จากเอกสารของไพ ธ อนนี่คือฟังก์ชั่นที่คุณต้องการ:

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

เหตุผลที่ง่าย__import__ไม่ทำงานเนื่องจากการนำเข้าสิ่งใดก็ตามที่ผ่านจุดแรกในสตริงแพคเกจเป็นคุณลักษณะของโมดูลที่คุณกำลังนำเข้า ดังนั้นสิ่งนี้จะไม่ทำงาน:

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

คุณต้องเรียกใช้ฟังก์ชันด้านบนดังนี้:

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

หรือในกรณีตัวอย่างของคุณ:

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

แก้ไข : ฉันเป็นบิตนี้ สิ่งที่คุณต้องการทำคือ:

from my_package.my_module import my_class

ฟังก์ชั่นข้างต้นเป็นสิ่งที่จำเป็นเท่านั้นถ้าคุณมีที่ว่างเปล่า fromlist ดังนั้นการโทรที่เหมาะสมจะเป็นเช่นนี้:

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

ฉันลอง my_import ('my_package.my_module.my_class') แต่ไม่พบโมดูลที่พบ my_class ซึ่งมีเหตุผลเพราะเป็นคลาสที่ไม่ใช่โมดูล Howver ถ้าฉันสามารถใช้ gettattr ที่จะได้รับการเรียนหลังจากที่โทรไป my_import
pjesi

มันแปลกมาก ทุกสิ่งทุกอย่างที่ผ่านจุดแรกนั้นถูกเรียกใช้ ไม่ควรมีความแตกต่าง
เจสันเบเกอร์

ขอบคุณฉันคิดว่านี่เป็นวิธีที่ดีที่สุด ตอนนี้ผมเพียง แต่ต้องมีวิธีที่ดีที่สุดที่จะแยกสตริง 'my_pakcage.my_module.my_class' ลง MOD_NAME, klass_name แต่ผมคิดว่าผมสามารถคิดออกว่า :)
pjesi

2
เอกสารหลาม (บนรหัส) ของการนำเข้าบอกว่าจะใช้ importlib ดังนั้นควรเช็คเอาท์คำตอบโดย Adam Spence
naoko


125

หากคุณไม่ต้องการที่จะม้วนตัวเองมีฟังก์ชั่นที่มีอยู่ในpydocโมดูลที่ทำสิ่งนี้:

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

ข้อดีของวิธีนี้มากกว่าคนอื่น ๆ ที่ระบุไว้ที่นี่เป็นที่locateจะพบใด ๆวัตถุหลามที่เส้นทางประให้ไม่ได้เป็นเพียงวัตถุโดยตรงภายในโมดูล my_package.my_module.MyClass.attrเช่น

หากคุณอยากรู้ว่าสูตรของพวกเขาคืออะไรนี่คือหน้าที่:

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.safeimportฟังก์ชั่น นี่คือเอกสารที่:

"""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
ฉัน upvoted คำตอบนี้ BTW นี่คือรหัสที่ยังมี safeimport เป็นดูเหมือนว่าแปลกที่ pydoc นำเข้าเพียงสำหรับการนี้: github.com/python/cpython/blob/...
brianray

ฉันก็ตอบคำตอบนี้ด้วยนี่เป็นคำตอบที่ดีที่สุดของทั้งหมดที่เกี่ยวข้อง
Sunding Wei

ดูเหมือนว่าจะสามารถจัดการqualname(วัตถุที่ไม่ได้อยู่ที่ด้านบนของโมดูลเนมสเปซ) ได้อย่างถูกต้องเช่นกัน
MisterMiyagi

ใช่คุณสามารถทำได้locate('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
เมื่อคุณนำเข้าโมดูลแบบไดนามิกแล้วคุณจะสามารถเข้าถึงชั้นเรียนผ่านโมดูลได้
Adam Spence

เพิ่งแก้ไขคำตอบของฉันให้กระชับยิ่งขึ้น นี่เป็นวิธีที่ดีที่สุดในการโหลดคลาสใน Python
Adam Spence

BBB-Benny และ Spence! ;)
dKen

ที่ดี! มันเป็นส่วนหนึ่งของไลบรารี่มาตรฐานตั้งแต่ 2.7 ขึ้นไป
Apteryx

นี่เป็นวิธีที่ถูกต้องในการเข้าถึงโมดูล / คลาส เอกสารระบุไว้ที่นี่: docs.python.org/3/library/importlib.html#importlib.__import__
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
นี่คือทางออกที่สะอาด! คุณสามารถลองใช้:(modulename, classname) = cl.rsplit('.', 2)
vdboor

มันยอดเยี่ยมมาก) ฉันได้สร้างแพคเกจแบบพัตต์ที่มีอุปกรณ์ที่แตกต่างกันรวมทั้งการนำเข้าคลาสด้วย ถ้าคุณต้องการคุณสามารถใช้มันจากแพ็คเกจนั้น
สแตน

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

ฉันจัดการเพื่อผ่าน {} แทน Globals / ชาวบ้านและยังคงทำงานได้ดี
valtron

18

หากคุณใช้ Django คุณสามารถใช้สิ่งนี้ ใช่ฉันรู้ว่า OP ไม่ได้ขอ django แต่ฉันวิ่งข้ามคำถามนี้เพื่อหาวิธีแก้ปัญหา Django ไม่พบและวางไว้ที่นี่เพื่อเด็กชายคนใหม่ / สาวที่มองหามัน

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

โปรดทราบว่าหากคุณต้องการนำเข้าบางสิ่งที่ไม่มี.เช่นreหรือargparseใช้:

re = __import__('re')

6

นี่คือการแบ่งปันสิ่งที่ฉันพบใน__import__และimportlibในขณะที่พยายามที่จะแก้ปัญหานี้

ฉันใช้ Python 3.7.3

เมื่อฉันพยายามที่จะได้รับในชั้นเรียนdในโมดูลa.b.c,

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

modตัวแปรอ้างถึง anamespace

ดังนั้นเพื่อไปเรียนdฉันต้อง

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

ถ้าเราพยายามทำ

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

a.dเราเป็นจริงพยายามที่จะมองหา

เมื่อใช้importlibฉันคิดว่าgetattrไลบรารี่ทำซ้ำซ้ำแล้วซ้ำอีกสำหรับเรา ดังนั้นเมื่อเราใช้importlib.import_moduleเราจะได้รับการจัดการในโมดูลที่ลึกที่สุด

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

1

ตกลงสำหรับฉันนั่นคือวิธีการทำงาน (ฉันใช้ Python 2.7):

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

จากนั้น b เป็นตัวอย่างของคลาส 'MyClass'


0

หากคุณมีอินสแตนซ์ของคลาสที่คุณต้องการอยู่แล้วคุณสามารถใช้ฟังก์ชัน 'type' เพื่อแยกประเภทคลาสและใช้เพื่อสร้างอินสแตนซ์ใหม่:

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
module = __import__("my_package/my_module")
the_class = getattr(module, "MyClass")
obj = the_class()

5
โปรดทราบว่าสิ่งนี้ใช้งานได้เนื่องจากมีข้อบกพร่องในฟังก์ชันนำเข้า ไม่ควรใช้พา ธ ไฟล์ในฟังก์ชั่นการนำเข้าและจะไม่ทำงานใน python 2.6 ขึ้นไป: docs.python.org/whatsnew/2.6.html#porting-to-python-2-6
Jason Baker

-2

ใน Google App Engine มีฟังก์ชั่นที่เรียกว่าwebapp2 import_stringสำหรับข้อมูลเพิ่มเติมดูที่นี่: https://webapp-improved.appspot.com/api/webapp2.html

ดังนั้น,

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

ตัวอย่างนี้ใช้ในwebapp2.Routeตำแหน่งที่คุณสามารถใช้ตัวจัดการหรือสตริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.