Chuyển đổi chuỗi thành đối tượng lớp Python?


187

Đưa ra một chuỗi làm đầu vào của người dùng cho hàm Python, tôi muốn lấy một đối tượng lớp ra khỏi nó nếu có một lớp có tên đó trong không gian tên được xác định hiện tại. Về cơ bản, tôi muốn triển khai một hàm sẽ tạo ra loại kết quả này:

class Foo:
    pass

str_to_class("Foo")
==> <class __main__.Foo at 0x69ba0>

Đây có phải là ở tất cả có thể?

python 

2
Có một số câu trả lời hữu ích ở đây, nhưng tôi thấy câu trả lời cho câu hỏi này đặc biệt hữu ích: stackoverflow.com/questions/452969/ợi
Tom

Câu trả lời:


115

Cảnh báo : eval()có thể được sử dụng để thực thi mã Python tùy ý. Bạn không bao giờ nên sử dụng eval()với các chuỗi không đáng tin cậy. (Xem Bảo mật của Python's eval () trên các chuỗi không tin cậy? )

Điều này có vẻ đơn giản nhất.

>>> class Foo(object):
...     pass
... 
>>> eval("Foo")
<class '__main__.Foo'>

30
Beaware sự phân nhánh của việc sử dụng eval. Bạn nên chắc chắn rằng chuỗi bạn truyền vào không phải từ người dùng.
Ép xung

18
Sử dụng eval () để mở cửa để thực thi mã tùy ý, vì mục đích bảo mật nên tránh.
m.kocikowski

54
Eval không để cửa mở cho bất cứ điều gì. Nếu bạn có những người dùng độc hại, độc ác, những người có thể độc hại và xấu xa sẽ chuyển các giá trị xấu sang eval, họ chỉ có thể chỉnh sửa nguồn python. Vì họ chỉ có thể chỉnh sửa nguồn python, cánh cửa đã, đang và sẽ luôn mở.
S.Lott

34
Trừ khi chương trình trong câu hỏi đang chạy trên một máy chủ.
Vatsu1

12
@ s-lott Tôi xin lỗi, nhưng tôi nghĩ bạn đang đưa ra lời khuyên rất tệ ở đây. Xem xét: Khi máy tính của bạn nhắc bạn nhập mật khẩu, người dùng độc hại, độc hại cũng có thể sửa đổi thư viện hoặc thư viện thực thi tương ứng và bỏ qua kiểm tra này. Do đó, người ta có thể lập luận rằng việc thêm kiểm tra mật khẩu là vô nghĩa. Hoặc là nó?
Daniel Sk

148

Điều này có thể làm việc:

import sys

def str_to_class(classname):
    return getattr(sys.modules[__name__], classname)

Điều gì sẽ xảy ra nếu lớp không tồn tại?
riza

7
Nó sẽ chỉ hoạt động cho lớp được xác định trong mô-đun hiện tại
luc

1
Giải pháp thực sự tốt đẹp: ở đây bạn không sử dụng nhập mà là một mô-đun sys chung hơn.
Stefano Falsetto

Điều này sẽ khác với def str_to_class(str): return vars()[str]?
Arne

113

Bạn có thể làm một cái gì đó như:

globals()[class_name]

6
Tôi thích điều này tốt hơn giải pháp 'eval' bởi vì (1) nó dễ như vậy và (2) nó không để bạn mở để thực thi mã tùy ý.
mjumbewu

2
nhưng bạn đang sử dụng globals()... một điều khác nên tránh
Greg

5
@Greg Có lẽ, nhưng globals()được cho là ít tệ hơn nhiều eval, và không thực sự tệ hơn sys.modules[__name__]AFAIK.
Laurence Gonsalves

2
Vâng tôi thấy quan điểm của bạn, bởi vì bạn chỉ lấy từ nó mà không thiết lập bất cứ điều gì
Greg

Điều gì xảy ra nếu biến được sử dụng trong một lớp và nhiều trường hợp cho lớp đó được thực hiện đồng thời cùng một lúc. Nó sẽ gây ra vấn đề, vì đây là một biến toàn cầu.
Alok Kumar Singh

98

Bạn muốn lớp Baz, sống trong mô-đun foo.bar. Với Python 2.7, bạn muốn sử dụng importlib.import_module(), vì điều này sẽ giúp việc chuyển đổi sang Python 3 dễ dàng hơn:

import importlib

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = importlib.import_module(module_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

Với Python <2.7:

def class_for_name(module_name, class_name):
    # load the module, will raise ImportError if module cannot be loaded
    m = __import__(module_name, globals(), locals(), class_name)
    # get the class, will raise AttributeError if class cannot be found
    c = getattr(m, class_name)
    return c

Sử dụng:

loaded_class = class_for_name('foo.bar', 'Baz')

19
import sys
import types

def str_to_class(field):
    try:
        identifier = getattr(sys.modules[__name__], field)
    except AttributeError:
        raise NameError("%s doesn't exist." % field)
    if isinstance(identifier, (types.ClassType, types.TypeType)):
        return identifier
    raise TypeError("%s is not a class." % field)

Điều này xử lý chính xác cả các lớp kiểu cũ và kiểu mới.


Bạn không cần loại.TypeType; nó chỉ là một bí danh cho "loại" dựng sẵn.
Glenn Maynard

1
Vâng tôi biết, nhưng tôi cảm thấy nó chỉ rõ ràng hơn để đọc. Tôi đoán rằng nó chỉ sôi sục theo sở thích, mặc dù.
Evan Fosmark

17

Tôi đã xem cách django xử lý việc này

django.utils.module_loading có cái này

def import_string(dotted_path):
    """
    Import a dotted module path and return the attribute/class designated by the
    last name in the path. Raise ImportError if the import failed.
    """
    try:
        module_path, class_name = dotted_path.rsplit('.', 1)
    except ValueError:
        msg = "%s doesn't look like a module path" % dotted_path
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

    module = import_module(module_path)

    try:
        return getattr(module, class_name)
    except AttributeError:
        msg = 'Module "%s" does not define a "%s" attribute/class' % (
            module_path, class_name)
        six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])

Bạn có thể sử dụng nó như import_string("module_path.to.all.the.way.to.your_class")



3

Vâng, bạn có thể làm điều này. Giả sử các lớp của bạn tồn tại trong không gian tên toàn cầu, một cái gì đó như thế này sẽ làm điều đó:

import types

class Foo:
    pass

def str_to_class(s):
    if s in globals() and isinstance(globals()[s], types.ClassType):
            return globals()[s]
    return None

str_to_class('Foo')

==> <class __main__.Foo at 0x340808cc>

1
Trong Python 3, không có ClassType nữa.
riza

1
Sử dụng isinstance (x, loại).
Glenn Maynard

3

Về mặt thực thi mã tùy ý hoặc tên người dùng không mong muốn đã chuyển, bạn có thể có một danh sách các tên hàm / lớp được chấp nhận và nếu đầu vào khớp với một tên trong danh sách, thì đó là eval'd.

Tái bút: Tôi biết .... hơi muộn .... nhưng đó là cho bất kỳ ai khác tình cờ gặp phải điều này trong tương lai.


9
Nếu bạn sẽ duy trì danh sách, thay vào đó, có thể duy trì một lệnh từ tên đến lớp. Sau đó, không cần eval ().
Fasaxc

3

Sử dụng importlib làm việc tốt nhất cho tôi.

import importlib

importlib.import_module('accounting.views') 

Điều này sử dụng ký hiệu chấm chuỗi cho mô-đun python mà bạn muốn nhập.


3

Nếu bạn thực sự muốn truy xuất các lớp bạn tạo bằng một chuỗi, bạn nên lưu trữ (hoặc từ đúng, tham chiếu ) chúng trong từ điển. Rốt cuộc, điều đó cũng sẽ cho phép đặt tên các lớp của bạn ở cấp độ cao hơn và tránh để lộ các lớp không mong muốn.

Ví dụ, từ một trò chơi trong đó các lớp diễn viên được xác định bằng Python và bạn muốn tránh các lớp chung khác được tiếp cận bởi đầu vào của người dùng.

Một cách tiếp cận khác (như trong ví dụ dưới đây) sẽ tạo ra một lớp hoàn toàn mới, giữ nguyên dictở trên. Cái này sẽ:

  • Cho phép nhiều chủ sở hữu lớp được thực hiện để tổ chức dễ dàng hơn (như, một cho các lớp diễn viên và một cho các loại âm thanh);
  • Thực hiện sửa đổi cho cả chủ sở hữu và các lớp được tổ chức dễ dàng hơn;
  • Và bạn có thể sử dụng các phương thức lớp để thêm các lớp vào dict. (Mặc dù sự trừu tượng dưới đây không thực sự cần thiết, nó chỉ đơn thuần là để ... "minh họa" ).

Thí dụ:

class ClassHolder(object):
    def __init__(self):
        self.classes = {}

    def add_class(self, c):
        self.classes[c.__name__] = c

    def __getitem__(self, n):
        return self.classes[n]

class Foo(object):
    def __init__(self):
        self.a = 0

    def bar(self):
        return self.a + 1

class Spam(Foo):
    def __init__(self):
        self.a = 2

    def bar(self):
        return self.a + 4

class SomethingDifferent(object):
    def __init__(self):
        self.a = "Hello"

    def add_world(self):
        self.a += " World"

    def add_word(self, w):
        self.a += " " + w

    def finish(self):
        self.a += "!"
        return self.a

aclasses = ClassHolder()
dclasses = ClassHolder()
aclasses.add_class(Foo)
aclasses.add_class(Spam)
dclasses.add_class(SomethingDifferent)

print aclasses
print dclasses

print "======="
print "o"
print aclasses["Foo"]
print aclasses["Spam"]
print "o"
print dclasses["SomethingDifferent"]

print "======="
g = dclasses["SomethingDifferent"]()
g.add_world()
print g.finish()

print "======="
s = []
s.append(aclasses["Foo"]())
s.append(aclasses["Spam"]())

for a in s:
    print a.a
    print a.bar()
    print "--"

print "Done experiment!"

Điều này trả lại cho tôi:

<__main__.ClassHolder object at 0x02D9EEF0>
<__main__.ClassHolder object at 0x02D9EF30>
=======
o
<class '__main__.Foo'>
<class '__main__.Spam'>
o
<class '__main__.SomethingDifferent'>
=======
Hello World!
=======
0
1
--
2
6
--
Done experiment!

Một thử nghiệm thú vị khác để thực hiện với những phương pháp đó là thêm một phương thức dưa chua ClassHolderđể bạn không bao giờ mất tất cả các lớp bạn đã làm: ^)

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.