Gọi một chức năng của một mô-đun bằng cách sử dụng tên của nó (một chuỗi)


1735

Cách tốt nhất để gọi hàm là một chuỗi có tên của hàm trong chương trình Python là gì. Ví dụ: giả sử tôi có một mô-đun foovà tôi có một chuỗi có nội dung "bar". Cách tốt nhất để gọi là foo.bar()gì?

Tôi cần lấy giá trị trả về của hàm, đó là lý do tại sao tôi không chỉ sử dụng eval. Tôi đã tìm ra cách thực hiện bằng cách sử dụng evalđể xác định hàm tạm thời trả về kết quả của lệnh gọi hàm đó, nhưng tôi hy vọng rằng có một cách thanh lịch hơn để làm điều này.

Câu trả lời:


2079

Giả sử mô-đun foovới phương thức bar:

import foo
method_to_call = getattr(foo, 'bar')
result = method_to_call()

Bạn có thể rút ngắn dòng 2 và 3 thành:

result = getattr(foo, 'bar')()

nếu điều đó có ý nghĩa hơn cho trường hợp sử dụng của bạn.

Bạn có thể sử dụng getattrtheo cách này trên các phương thức ràng buộc thể hiện lớp, phương thức cấp mô-đun, phương thức lớp ... danh sách sẽ tiếp tục.


9
hasattr hoặc getattr có thể được sử dụng để xác định xem một hàm có được xác định không. Tôi đã có một ánh xạ cơ sở dữ liệu (eventType và xử lý functionName) và tôi muốn đảm bảo rằng tôi không bao giờ "quên" định nghĩa một trình xử lý sự kiện trong python của mình
Shaun

9
Điều này hoạt động nếu bạn đã biết tên mô-đun. Tuy nhiên, nếu bạn muốn người dùng cung cấp tên mô-đun dưới dạng chuỗi, điều này sẽ không hoạt động.
Blairg23

7
Nếu bạn cần tránh noneType không phải là ngoại lệ có thể gọi được, bạn cũng có thể sử dụng hình thức ba đối số của getattr: getattr (foo, 'bar', lambda: Không). Tôi xin lỗi vì định dạng; ứng dụng android stackexchange rõ ràng là khủng khiếp.
geekofalltrades

3
Xem thêm câu trả lời được cung cấp bởi @sastanin nếu bạn chỉ quan tâm ví dụ về các chức năng của mô đun cục bộ / hiện tại của bạn.
NuSkooler

6
@akki Vâng, nếu bạn đang ở trong các foomô-đun bạn có thể sử dụng globals()để làm điều này:methodToCall = globals()['bar']
Bến Hoyt

543
locals()["myfunction"]()

hoặc là

globals()["myfunction"]()

người dân địa phương trả về một từ điển với một bảng biểu tượng địa phương hiện tại. Globals lợi nhuận một cuốn từ điển với bảng biểu tượng toàn cầu.


53
Phương thức này với toàn cầu / địa phương là tốt nếu phương thức bạn cần gọi được xác định trong cùng một mô-đun bạn đang gọi từ đó.
Joelmob

@Joelmob có cách nào khác để lấy một đối tượng bằng chuỗi ra khỏi không gian tên gốc không?
Nick T

@NickT Tôi chỉ biết về các phương pháp này, tôi không nghĩ có bất kỳ phương pháp nào khác có chức năng tương tự như vậy, ít nhất tôi không thể nghĩ ra lý do tại sao nên có nhiều hơn.
Joelmob

Tôi có một lý do cho bạn (thực ra điều gì đã dẫn tôi đến đây): Mô-đun A có chức năng F cần gọi một chức năng theo tên. Mô-đun B nhập Mô-đun A và gọi hàm F với yêu cầu gọi Hàm G, được xác định trong Mô-đun B. Cuộc gọi này không thành công vì rõ ràng, hàm F chỉ chạy với các quả cầu được xác định trong Mô-đun F - vì vậy toàn cầu () ['G'] = Không có.
David Stein

337

Giải pháp của Patrick có lẽ là sạch nhất. Nếu bạn cũng cần phải tự động chọn mô-đun, bạn có thể nhập mô-đun như sau:

module = __import__('foo')
func = getattr(module, 'bar')
func()

93
Tôi không hiểu bình luận cuối cùng đó. __import__ có quyền riêng của mình và câu tiếp theo trong các tài liệu được đề cập nói: "Việc sử dụng trực tiếp __import __ () là rất hiếm, trừ trường hợp bạn muốn nhập mô-đun mà tên chỉ được biết khi chạy". Vì vậy: +1 cho câu trả lời đã cho.
hoffmaje

50
Sử dụng importlib.import_module. Các tài liệu chính thức nói về __import__: "Đây là một chức năng nâng cao không cần thiết trong lập trình Python hàng ngày, không giống như importlib.import_module ()." docs.python.org/2/library/functions.html#__import__
glarrain

8
@glarrain Miễn là bạn ổn với chỉ hỗ trợ 2.7 trở lên.
Xiong Chiamiov

1
@Xiong Chaimiov, importlib.import_moduleđược hỗ trợ trong 3.6. Xem docs.python.org/3.6/library/...
cowlinator

7
@cowlinator Có, 3.6 là một phần của "2.7 trở lên", cả về ngữ nghĩa phiên bản nghiêm ngặt và ngày phát hành (nó xuất hiện khoảng sáu năm sau). Nó cũng không tồn tại trong ba năm sau bình luận của tôi. ;) Trong nhánh 3.x, mô-đun đã có từ 3.1. 2.7 và 3.1 bây giờ khá cổ xưa; bạn vẫn sẽ thấy các máy chủ treo xung quanh chỉ hỗ trợ 2.6, nhưng có lẽ đáng để nhập khẩu là lời khuyên tiêu chuẩn hiện nay.
Xiong Chiamiov

114

Chỉ là một đóng góp đơn giản. Nếu lớp mà chúng ta cần thể hiện trong cùng một tệp, chúng ta có thể sử dụng một cái gì đó như thế này:

# Get class from globals and create an instance
m = globals()['our_class']()

# Get the function (from the instance) that we need to call
func = getattr(m, 'function_name')

# Call it
func()

Ví dụ:

class A:
    def __init__(self):
        pass

    def sampleFunc(self, arg):
        print('you called sampleFunc({})'.format(arg))

m = globals()['A']()
func = getattr(m, 'sampleFunc')
func('sample arg')

# Sample, all on one line
getattr(globals()['A'](), 'sampleFunc')('sample arg')

Và, nếu không phải là một lớp học:

def sampleFunc(arg):
    print('you called sampleFunc({})'.format(arg))

globals()['sampleFunc']('sample arg')

101

Đưa ra một chuỗi, với một đường dẫn python hoàn chỉnh đến một hàm, đây là cách tôi đi về kết quả của hàm đã nói:

import importlib
function_string = 'mypackage.mymodule.myfunc'
mod_name, func_name = function_string.rsplit('.',1)
mod = importlib.import_module(mod_name)
func = getattr(mod, func_name)
result = func()

1
Điều này đã giúp tôi. Đây là một phiên bản nhẹ của __import__chức năng.
Pankaj Bhambhani

Tôi nghĩ rằng đây là câu trả lời tốt nhất.
Saeid

55

Câu trả lời tốt nhất theo Câu hỏi thường gặp về lập trình Python sẽ là:

functions = {'myfoo': foo.bar}

mystring = 'myfoo'
if mystring in functions:
    functions[mystring]()

Ưu điểm chính của kỹ thuật này là các chuỗi không cần phải khớp với tên của các hàm. Đây cũng là kỹ thuật chính được sử dụng để mô phỏng cấu trúc trường hợp


43

Câu trả lời (tôi hy vọng) không ai muốn

Eval thích hành vi

getattr(locals().get("foo") or globals().get("foo"), "bar")()

Tại sao không thêm tự động nhập

getattr(
    locals().get("foo") or 
    globals().get("foo") or
    __import__("foo"), 
"bar")()

Trong trường hợp chúng tôi có thêm từ điển, chúng tôi muốn kiểm tra

getattr(next((x for x in (f("foo") for f in 
                          [locals().get, globals().get, 
                           self.__dict__.get, __import__]) 
              if x)),
"bar")()

Chúng ta cần đi sâu hơn

getattr(next((x for x in (f("foo") for f in 
              ([locals().get, globals().get, self.__dict__.get] +
               [d.get for d in (list(dd.values()) for dd in 
                                [locals(),globals(),self.__dict__]
                                if isinstance(dd,dict))
                if isinstance(d,dict)] + 
               [__import__])) 
        if x)),
"bar")()

điều này có thể được cải thiện bằng cách quét đệ quy cây thư mục và tự động gắn ổ đĩa USB
wilks

27

Đối với giá trị của nó, nếu bạn cần truyền tên hàm (hoặc lớp) và tên ứng dụng dưới dạng chuỗi, thì bạn có thể làm điều này:

myFnName  = "MyFn"
myAppName = "MyApp"
app = sys.modules[myAppName]
fn  = getattr(app,myFnName)

handler = getattr(sys.modules[__name__], myFnName)
Nói

21

Thử cái này. Trong khi điều này vẫn sử dụng eval, nó chỉ sử dụng nó để triệu tập chức năng từ bối cảnh hiện tại . Sau đó, bạn có chức năng thực sự để sử dụng như bạn muốn.

Lợi ích chính cho tôi từ điều này là bạn sẽ nhận được bất kỳ lỗi nào liên quan đến eval tại điểm triệu tập hàm. Sau đó, bạn sẽ chỉ nhận được các lỗi liên quan đến chức năng khi bạn gọi.

def say_hello(name):
    print 'Hello {}!'.format(name)

# get the function by name
method_name = 'say_hello'
method = eval(method_name)

# call it like a regular function later
args = ['friend']
kwargs = {}
method(*args, **kwargs)

1
Điều này sẽ có rủi ro. chuỗi có thể có bất cứ điều gì và eval sẽ kết thúc nó mà không cần xem xét.
iankit

4
Chắc chắn, bạn phải chú ý đến bối cảnh bạn đang sử dụng nó, liệu điều này có phù hợp hay không, với những rủi ro đó.
tvt173

1
Một hàm không chịu trách nhiệm xác thực các tham số của nó - đó là công việc của một hàm khác. Nói rằng thật nguy hiểm khi sử dụng eval với một chuỗi đang nói rằng việc sử dụng mọi chức năng là rủi ro.
red777

Bạn không bao giờ nên sử dụng evaltrừ khi thực sự cần thiết. getattr(__module__, method_name)là một lựa chọn tốt hơn nhiều trong bối cảnh này.
moi

14

không có gì được đề nghị giúp tôi Tôi đã khám phá điều này mặc dù.

<object>.__getattribute__(<string name>)(<params>)

Tôi đang sử dụng python 2.66

Hi vọng điêu nay co ich


13
Ở khía cạnh nào thì điều này tốt hơn getattr ()?
V13

1
Chính xác những gì tôi muốn. Hoạt động như một lá bùa! Hoàn hảo!! self.__getattribute__('title')tương đương vớiself.title
ioaniatr

self.__getattribute__('title')không hoạt động trong mọi trường hợp (không biết tại sao) sau đó, nhưng func = getattr(self, 'title'); func();không. Vì vậy, có lẽ tốt hơn để sử dụng getattr()thay thế
ioaniatr

10
Những người không biết trăn có thể ngừng nâng cấp rác này không? Sử dụng getattrthay thế.
Aran-Fey

6

Như câu hỏi này Làm thế nào để tự động gọi các phương thức trong một lớp bằng cách sử dụng gán tên phương thức cho một biến [trùng lặp] được đánh dấu là trùng lặp như phương thức này, tôi sẽ đăng một câu trả lời liên quan ở đây:

Kịch bản là, một phương thức trong một lớp muốn gọi một phương thức khác trên cùng một lớp một cách linh hoạt, tôi đã thêm một số chi tiết vào ví dụ ban đầu cung cấp một số kịch bản rộng hơn và rõ ràng hơn:

class MyClass:
    def __init__(self, i):
        self.i = i

    def get(self):
        func = getattr(MyClass, 'function{}'.format(self.i))
        func(self, 12)   # This one will work
        # self.func(12)    # But this does NOT work.


    def function1(self, p1):
        print('function1: {}'.format(p1))
        # do other stuff

    def function2(self, p1):
        print('function2: {}'.format(p1))
        # do other stuff


if __name__ == "__main__":
    class1 = MyClass(1)
    class1.get()
    class2 = MyClass(2)
    class2.get()

Đầu ra (Python 3.7.x)

chức năng1: 12

chức năng 2: 12


-7

Đây là một câu trả lời đơn giản, điều này sẽ cho phép bạn xóa màn hình chẳng hạn. Có hai ví dụ bên dưới, với eval và exec, sẽ in 0 ở trên cùng sau khi làm sạch (nếu bạn đang sử dụng Windows, thay đổi clearthành cls, người dùng Linux và Mac sẽ rời khỏi ví dụ) hoặc chỉ thực hiện nó, tương ứng.

eval("os.system(\"clear\")")
exec("os.system(\"clear\")")

3
Đây không phải là những gì op yêu cầu.
Tuncay Göncüoğlu
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.