Python có tương đương với Java Class.forName () không?


95

Tôi có nhu cầu lấy một đối số chuỗi và tạo một đối tượng của lớp có tên trong chuỗi đó bằng Python. Trong Java, tôi sẽ sử dụng Class.forName().newInstance(). Có tương đương trong Python không?


Cảm ơn vì những câu trả lời. Để trả lời những người muốn biết tôi đang làm gì: Tôi muốn sử dụng đối số dòng lệnh làm tên lớp và khởi tạo nó. Tôi thực sự đang lập trình trong Jython và khởi tạo các lớp Java, do đó, câu hỏi về Java. getattr()hoạt động tốt. Cảm ơn nhiều.


Xem stackoverflow.com/a/10675081/116891 để biết ví dụ về cách sử dụng importlib.import, được báo cáo ngược từ python 3 đến 2.7 ( docs.python.org/2/library/importlib.html )
Pat

Câu trả lời:


169

Phản chiếu trong python dễ dàng và linh hoạt hơn rất nhiều so với trong Java.

Tôi khuyên bạn nên đọc hướng dẫn này

Không có hàm trực tiếp nào (mà tôi biết) lấy tên lớp đủ điều kiện và trả về lớp, tuy nhiên bạn có tất cả các phần cần thiết để xây dựng nó và bạn có thể kết nối chúng với nhau.

Tuy nhiên, có một lời khuyên: đừng cố gắng lập trình theo kiểu Java khi bạn đang ở trong python.

Nếu bạn có thể giải thích những gì bạn đang cố gắng làm, có thể chúng tôi có thể giúp bạn tìm ra cách làm điều đó hiệu quả hơn.

Đây là một chức năng thực hiện những gì bạn muốn:

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

Bạn có thể sử dụng giá trị trả về của hàm này như thể nó là chính lớp.

Đây là một ví dụ sử dụng:

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

Nó hoạt động như thế nào?

Chúng tôi đang sử dụng __import__để nhập mô-đun chứa lớp, điều này yêu cầu trước tiên chúng tôi trích xuất tên mô-đun từ tên đủ điều kiện. Sau đó, chúng tôi nhập mô-đun:

m = __import__( module )

Trong trường hợp này, msẽ chỉ tham chiếu đến mô-đun cấp cao nhất,

Ví dụ: nếu lớp của bạn nằm trong foo.bazmô-đun, thì msẽ là mô-đun. foo
Chúng tôi có thể dễ dàng lấy tham chiếu để foo.bazsử dụnggetattr( m, 'baz' )

Để chuyển từ mô-đun cấp cao nhất đến lớp, phải sử dụng đệ quy gettatr trên các phần của tên lớp

Ví dụ: nếu tên lớp của bạn là foo.baz.bar.Modelthì chúng tôi thực hiện điều này:

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

Đây là những gì đang xảy ra trong vòng lặp này:

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

Ở cuối vòng lặp, msẽ là một tham chiếu đến lớp. Điều này có nghĩa là nó mthực sự là lớp itslef, bạn có thể làm như sau:

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

6
Exec là cực kỳ nguy hiểm. Thật dễ dàng để tự bắn vào chân mình bằng cách ghi đè lên các tên trong phạm vi của bạn một cách linh hoạt và cũng dễ dàng mở ra một lỗ hổng bảo mật có kích thước như Rhode Island.
Cody Brocious

1
Lý do thực thi không an toàn ở đây là bạn có thể sử dụng dấu ';' trong tên lớp để thoát ra khỏi quá trình nhập. Điều này có thể dễ dàng cho phép thực thi mã tùy ý. Lý do nó có thể làm hỏng mã của bạn là do tệp thi hành sẽ ghi đè các tên va chạm, gây ra lỗi và / hoặc lỗi bảo mật.
Cody Brocious

8
Đúng, đây là những gì __import__tồn tại cho. Các dự án như Django làm ma thuật tải mô-đun sử dụng nó mọi lúc. Câu trả lời rất hay.
cdleary

5
get_class = lambda name: reduce(getattr, name.split('.')[1:], __import__(name.partition('.')[0])). Mặc dù 'object.from_name' có thể là một tên tốt hơn. Ví dụ: get_class ('decimal.Decimal'), get_class (module_name).
jfs

1
Nó chỉ mất để xác định một hàm bảy dòng. Một sự dễ dàng thực sự.
Pavel Vlasov

27

Giả sử lớp nằm trong phạm vi của bạn:

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

Nếu không thì:

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

Chỉnh sửa: Lưu ý, bạn không thể đặt tên như 'foo.bar' cho getattr. Bạn sẽ cần phải chia nó theo. và gọi getattr () trên mỗi phần từ trái sang phải. Điều này sẽ xử lý rằng:

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

Nó không phải là hình cầu () ['classname']?
orip

Bạn thực sự chính xác. Tôi quên mất khối cầu () không trả về phạm vi toàn cục thực tế, chỉ là một ánh xạ của nó. Chỉnh sửa như vậy - cảm ơn!
Cody Brocious

Điều này không tương đương với Class.forName của java, trong Java, không có giả định nào được đưa ra về những gì được nhập và những gì không
hasen

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

1
Hoặcmodule, _, 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

Sử dụng

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 Đối với việc sử dụng importlib để thực hiện việc này vì nó ngăn chặn nhu cầu ( nguyên nhân nhập khẩu ) phải tìm một cách đệ quy lớp. Đơn giản hơn và rõ ràng hơn câu trả lời được chấp nhận (mặc dù ít được tài liệu hơn).
sage88

4

Tuy nhiên, một triển khai khác.

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)

Các if module_nameLBYL là khá redudant, vì lỗi mà bạn nhận được nếu bạn chỉ cần xóa những dòng này là: ValueError: Empty module name.
bukzor

3

Có vẻ như bạn đang tiếp cận điều này từ giữa thay vì đầu. Bạn thực sự đang cố gắng làm gì? Tìm lớp liên kết với một chuỗi nhất định là một phương tiện để kết thúc.

Nếu bạn làm rõ vấn đề của mình, điều này có thể đòi hỏi bạn phải tái cấu trúc lại tinh thần, thì một giải pháp tốt hơn có thể tự xuất hiện.

Ví dụ: Bạn đang cố gắng tải một đối tượng đã lưu dựa trên tên kiểu của nó và một tập hợp các tham số? Python đánh vần điều này và bạn nên nhìn vào mô-đun dưa chua . Và mặc dù quá trình giải nén thực hiện chính xác những gì bạn mô tả, bạn không phải lo lắng về cách nó hoạt động bên trong:

>>> 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
điều này rất thú vị, nhưng hơi khác so với câu hỏi
Nick

1

Điều này được tìm thấy trong thư viện tiêu chuẩn của python, dưới dạng unittest.TestLoader.loadTestsFromName. Thật không may, phương pháp tiếp tục thực hiện các hoạt động bổ sung liên quan đến thử nghiệm, nhưng ha đầu tiên này có vẻ có thể sử dụng lại được. Tôi đã chỉnh sửa nó để xóa chức năng liên quan đến thử nghiệm:

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

Tôi cần lấy các đối tượng cho tất cả các lớp hiện có trong my_package. Vì vậy, tôi nhập khẩu tất cả các lớp học cần thiết vào my_package's__init__.py .

Vì vậy, cấu trúc thư mục của tôi như thế này:

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

Và tôi __init__.pytrông như thế này:

from .module1 import ClassA
from .module2 import ClassB

Sau đó, tôi tạo một hàm như sau:

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

Ở đâu module_name = 'my_package'

kiểm tra tài liệu: https://docs.python.org/3/library/inspect.html#inspect.getmembers

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.