python มีคุณสมบัติเทียบเท่ากับ Java Class.forName () หรือไม่


95

ฉันมีความต้องการที่จะใช้อาร์กิวเมนต์สตริงและสร้างวัตถุของคลาสที่มีชื่อในสตริงนั้นใน Python ใน Java ฉันจะใช้Class.forName().newInstance(). มีความเท่าเทียมกันใน Python หรือไม่?


ขอบคุณสำหรับคำตอบ เพื่อตอบผู้ที่ต้องการทราบว่าฉันกำลังทำอะไรอยู่: ฉันต้องการใช้อาร์กิวเมนต์บรรทัดคำสั่งเป็นชื่อคลาสและสร้างอินสแตนซ์ จริงๆแล้วฉันกำลังเขียนโปรแกรมใน Jython และสร้างอินสแตนซ์คลาส Java ด้วยเหตุนี้ Java-ness ของคำถาม getattr()ใช้งานได้ดี ขอบคุณมาก.


ดูตัวอย่างการใช้stackoverflow.com/a/10675081/116891importlib.importซึ่ง backported จาก python 3 ถึง 2.7 ( docs.python.org/2/library/importlib.html )
Pat

คำตอบ:


169

การสะท้อนใน python นั้นง่ายกว่าและยืดหยุ่นกว่าใน Java มาก

ขอแนะนำให้อ่านบทแนะนำนี้

ไม่มีฟังก์ชันโดยตรง (ที่ฉันรู้จัก) ซึ่งใช้ชื่อคลาสแบบเต็มและส่งคืนคลาสอย่างไรก็ตามคุณมีทุกส่วนที่จำเป็นในการสร้างสิ่งนั้นและคุณสามารถเชื่อมต่อเข้าด้วยกันได้

คำแนะนำเล็กน้อย: อย่าพยายามเขียนโปรแกรมในรูปแบบ Java เมื่อคุณอยู่ใน python

หากคุณสามารถอธิบายได้ว่าคุณกำลังพยายามทำอะไรอยู่บางทีเราอาจช่วยคุณหาวิธีทำแบบไพ ธ อนิกได้มากขึ้น

นี่คือฟังก์ชันที่ทำในสิ่งที่คุณต้องการ:

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

คุณสามารถใช้ค่าส่งกลับของฟังก์ชันนี้ราวกับว่าเป็นคลาสนั้นเอง

นี่คือตัวอย่างการใช้งาน:

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

ทำงานอย่างไร?

เรากำลังใช้__import__เพื่ออิมพอร์ตโมดูลที่มีคลาสซึ่งจำเป็นต้องแยกชื่อโมดูลออกจากชื่อแบบเต็มก่อน จากนั้นเรานำเข้าโมดูล:

m = __import__( module )

ในกรณีนี้mจะอ้างถึงโมดูลระดับบนสุดเท่านั้น

ตัวอย่างเช่นหากชั้นเรียนของคุณอาศัยอยู่ในfoo.bazโมดูลmจะเป็นโมดูลที่foo
เราสามารถขอข้อมูลอ้างอิงเพื่อfoo.bazใช้getattr( m, 'baz' )

ในการรับจากโมดูลระดับบนสุดไปยังคลาสต้องใช้ซ้ำgettatrกับส่วนของชื่อคลาส

ยกตัวอย่างเช่นถ้าคุณมีชื่อคลาสfoo.baz.bar.Modelเราจะทำดังนี้

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

นี่คือสิ่งที่เกิดขึ้นในลูปนี้:

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

ในตอนท้ายของลูปmจะเป็นการอ้างอิงถึงคลาส ซึ่งหมายความว่าmจริงๆแล้วมันเป็นคลาสที่คุณสามารถทำได้เช่น:

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

6
Exec เป็นอันตรายอย่างยิ่ง ง่ายมากที่จะยิงตัวเองด้วยการเขียนทับชื่อในขอบเขตของคุณแบบไดนามิกและเปิดข้อบกพร่องด้านความปลอดภัยขนาดเท่าโรดไอส์แลนด์ได้อย่างง่ายดาย
Serafina Brocious

1
เหตุผลที่ผู้บริหารไม่ปลอดภัยที่นี่คือคุณสามารถใช้เครื่องหมาย ';' ในชื่อคลาสเพื่อแยกออกจากการนำเข้า สิ่งนี้สามารถอนุญาตให้ใช้รหัสโดยอำเภอใจได้อย่างง่ายดาย สาเหตุที่ทำให้โค้ดของคุณเสียหายคือ exec จะเขียนทับชื่อที่ชนกันทำให้เกิดข้อบกพร่องและ / หรือข้อบกพร่องด้านความปลอดภัย
Serafina Brocious

8
ใช่นี่คือสิ่งที่__import__มีอยู่เพื่อ โปรเจ็กต์อย่าง Django ที่ทำ module-loading-magic จะใช้มันตลอดเวลา คำตอบที่ดี
cdleary

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). แม้ว่า "object.from_name" อาจเป็นชื่อที่ดีกว่า ตัวอย่าง: get_class ('decimal.Decimal'), get_class (module_name)
jfs

1
ใช้เพื่อกำหนดฟังก์ชันเจ็ดบรรทัดเท่านั้น ง่ายอย่างแท้จริง
Pavel Vlasov

27

สมมติว่าชั้นเรียนอยู่ในขอบเขตของคุณ:

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

มิฉะนั้น:

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

แก้ไข: หมายเหตุคุณไม่สามารถตั้งชื่อเช่น 'foo.bar' ให้กับ getattr ได้ คุณจะต้องแยกมันด้วย และเรียก getattr () ในแต่ละชิ้นจากซ้ายไปขวา สิ่งนี้จะจัดการกับสิ่งนั้น:

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

มันควรเป็น globals () ['classname'] ไม่ใช่หรือ?
orip

คุณถูกต้องแน่นอน ฉันลืม globals () ไม่ได้ส่งคืนขอบเขตส่วนกลางที่แท้จริงเพียงแค่การทำแผนที่เท่านั้น การแก้ไขเช่นนี้ - ขอบคุณ!
Serafina Brocious

สิ่งนี้ไม่เทียบเท่ากับ Class.forName ของ java ใน Java ไม่มีการตั้งสมมติฐานเกี่ยวกับสิ่งที่นำเข้าและสิ่งที่ไม่ได้
hasen

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

1
หรือmodule, _, 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

การใช้งาน

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 สำหรับการใช้ importlib เพื่อทำสิ่งนี้เนื่องจากป้องกันไม่ให้จำเป็น ( สาเหตุการนำเข้านั้น) ในการค้นหาคลาสซ้ำ ง่ายและสะอาดกว่าคำตอบที่ยอมรับ (แม้ว่าจะมีเอกสารที่ดีน้อยกว่า)
sage88

4

ยังใช้งานได้อีก

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_nameLBYL เป็น redudant ValueError: Empty module nameสวยเนื่องจากข้อผิดพลาดที่คุณได้รับถ้าคุณเพียงแค่ลบเส้นที่เป็น:
bukzor

3

ดูเหมือนว่าคุณกำลังเข้าใกล้สิ่งนี้จากตรงกลางแทนที่จะเป็นจุดเริ่มต้น คุณกำลังพยายามทำอะไรอยู่? การค้นหาคลาสที่เกี่ยวข้องกับสตริงที่กำหนดเป็นวิธีการสิ้นสุด

หากคุณชี้แจงปัญหาของคุณซึ่งอาจต้องปรับสภาพจิตใจของคุณเองทางออกที่ดีกว่าอาจนำเสนอตัวเอง

ตัวอย่างเช่นคุณกำลังพยายามโหลดอ็อบเจ็กต์ที่บันทึกตามชื่อประเภทและชุดพารามิเตอร์หรือไม่? งูหลามคาถา unpickling นี้และคุณควรจะดูที่โมดูลดอง และถึงแม้ว่าขั้นตอนการปลดล็อคจะเป็นไปตามที่คุณอธิบาย แต่คุณก็ไม่ต้องกังวลเกี่ยวกับวิธีการทำงานภายใน:

>>> 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
สิ่งนี้น่าสนใจมาก แต่แตกต่างจากคำถามเล็กน้อย
นิค

1

พบได้ในไลบรารีมาตรฐาน python เช่น unittest.TestLoader.loadTestsFromName น่าเสียดายที่วิธีนี้ใช้ในการทำกิจกรรมที่เกี่ยวข้องกับการทดสอบเพิ่มเติม แต่ฮาแรกนี้กลับมาใช้งานได้ ฉันได้แก้ไขเพื่อลบฟังก์ชันที่เกี่ยวข้องกับการทดสอบ:

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

ฉันต้องการรับวัตถุสำหรับคลาสที่มีอยู่ทั้งหมดในmy_package. ดังนั้นผมจึงนำเข้าชั้นเรียนที่จำเป็นทั้งหมดลงใน'smy_package__init__.py

โครงสร้างไดเร็กทอรีของฉันจึงเป็นดังนี้:

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

และ__init__.pyหน้าตาของฉันเป็นแบบนี้:

from .module1 import ClassA
from .module2 import ClassB

จากนั้นฉันจะสร้างฟังก์ชันดังนี้:

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

ที่ไหน module_name = 'my_package'

ตรวจสอบเอกสาร: https://docs.python.org/3/library/inspect.html#inspect.getmembers

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