Wywoływanie funkcji modułu przy użyciu jego nazwy (ciągu)


1734

Jaki jest najlepszy sposób na wywołanie funkcji o podanym ciągu znaków z nazwą funkcji w programie Python. Na przykład, powiedzmy, że mam moduł fooi mam ciąg znaków, którego treść jest "bar". Jak najlepiej zadzwonić foo.bar()?

Potrzebuję uzyskać wartość zwracaną przez funkcję, dlatego po prostu jej nie używam eval. Zrozumiałem, jak to zrobić, używając evalfunkcji temp, która zwraca wynik tego wywołania funkcji, ale mam nadzieję, że istnieje bardziej elegancki sposób na zrobienie tego.

Odpowiedzi:


2078

Zakładając moduł fooz metodą bar:

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

Możesz skrócić linie 2 i 3, aby:

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

jeśli ma to większy sens w twoim przypadku użycia.

Możesz używać getattrw ten sposób metod związanych z instancjami klas, metod na poziomie modułów, metod klas ... lista jest długa.


9
hasattr lub getattr mogą być użyte do ustalenia, czy funkcja jest zdefiniowana. Miałem mapowanie bazy danych (eventType i nazwa funkcji obsługi) i chciałem się upewnić, że nigdy nie „zapomnę” zdefiniować procedury obsługi zdarzeń w moim pythonie
Shaun

9
Działa to, jeśli znasz już nazwę modułu. Jeśli jednak chcesz, aby użytkownik podał nazwę modułu jako ciąg, to nie zadziała.
Blairg23,

7
Aby uniknąć wyjątku NoneType, którego nie można wywołać, można również zastosować trzyargumentową formę getattr: getattr (foo, 'bar', lambda: None). Przepraszam za formatowanie; Aplikacja Android Stackexchange jest podobno straszna.
geekofalltrades

3
Zobacz także odpowiedź udzieloną przez @sastanin, jeśli dbasz na przykład o funkcje modułu lokalnego / bieżącego.
NuSkooler,

6
@akki Tak, jeśli jesteś w tym foomodule można użyć globals(), aby to zrobić:methodToCall = globals()['bar']
Ben Hoyt

543
locals()["myfunction"]()

lub

globals()["myfunction"]()

locals zwraca słownik z bieżącą lokalną tablicą symboli. globals zwraca słownik z globalną tablicą symboli.


53
Ta metoda z globals / locals jest dobra, jeśli metoda, którą musisz wywołać, jest zdefiniowana w tym samym module, z którego dzwonisz.
Joelmob,

@Jelmob jest jakiś inny sposób na uzyskanie obiektu poprzez ciąg znaków z głównej przestrzeni nazw?
Nick T

@NickT Znam tylko te metody, nie sądzę, aby były inne, które pełnią takie same funkcje, przynajmniej nie mogę wymyślić powodu, dla którego powinno być ich więcej.
Joelmob

Mam dla ciebie powód (właściwie co mnie tu doprowadziło): Moduł A ma funkcję F, która musi wywoływać funkcję po nazwie. Moduł B importuje moduł A i wywołuje funkcję F z żądaniem wywołania funkcji G, która jest zdefiniowana w module B. To wywołanie kończy się niepowodzeniem, ponieważ najwyraźniej funkcja F działa tylko z globałami zdefiniowanymi w module F - więc globals () [„G”] = ​​Brak.
David Stein

337

Rozwiązanie Patricka jest prawdopodobnie najczystsze. Jeśli chcesz również dynamicznie podnieść moduł, możesz go zaimportować w następujący sposób:

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

93
Nie rozumiem tego ostatniego komentarza. __import__ ma swoje prawa, a następne zdanie we wspomnianych dokumentach mówi: „Bezpośrednie użycie __importu __ () jest rzadkie, z wyjątkiem przypadków, gdy chcesz zaimportować moduł, którego nazwa jest znana tylko w czasie wykonywania”. Więc: +1 za podaną odpowiedź.
hoffmaje

50
Zastosowanie importlib.import_module. Oficjalne dokumenty mówią o __import__: „Jest to zaawansowana funkcja, która nie jest potrzebna w codziennym programowaniu w języku Python, w przeciwieństwie do importlib.import_module ().” docs.python.org/2/library/functions.html#__import__
glarrain

8
@glarrain Dopóki wszystko jest w porządku z obsługą tylko wersji 2.7 i nowszych.
Xiong Chiamiov

1
@Xiong Chaimiov, importlib.import_modulejest obsługiwany w wersji 3.6. Zobacz docs.python.org/3.6/library/…
cowlinator

7
@cowlinator Tak, 3.6 jest częścią „2.7 i nowszych”, zarówno w ścisłej semantyce wersjonowania, jak i datach wydania (pojawiło się około sześć lat później). Nie istniało też przez trzy lata po moim komentarzu. ;) W gałęzi 3.x moduł istnieje od wersji 3.1. 2.7 i 3.1 są teraz dość starożytne; nadal znajdziesz serwery, które obsługują tylko 2.6, ale prawdopodobnie warto teraz importowaćb jako standardową poradę.
Xiong Chiamiov

113

Tylko prosty wkład. Jeśli klasa, którą musimy wprowadzić, znajduje się w tym samym pliku, możemy użyć czegoś takiego:

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

Na przykład:

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

A jeśli nie klasa:

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

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

100

Biorąc pod uwagę ciąg znaków wraz z pełną ścieżką do funkcji w języku Python, w ten sposób zacząłem uzyskiwać wynik tej funkcji:

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
To mi pomogło. Jest to lekka wersja __import__funkcji.
Pankaj Bhambhani,

Myślę, że to była najlepsza odpowiedź.
Saeid


43

Odpowiedź (mam nadzieję) nikt nigdy nie chciał

Zachowanie przypominające ewolucję

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

Dlaczego nie dodać automatycznego importowania

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

Jeśli mamy dodatkowe słowniki, które chcemy sprawdzić

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

Musimy zejść głębiej

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

można to poprawić poprzez rekurencyjne skanowanie drzewa katalogów i automatyczne montowanie napędów USB
wilks

27

Jeśli chodzi o wartość, jeśli chcesz przekazać nazwę funkcji (lub klasy) i nazwę aplikacji jako ciąg, możesz to zrobić:

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

Nieco bardziej ogólny jesthandler = getattr(sys.modules[__name__], myFnName)
lony

21

Spróbuj tego. Chociaż nadal używa eval, używa go tylko do przywołania funkcji z bieżącego kontekstu . Następnie masz prawdziwą funkcję do użycia, jak chcesz.

Główną korzyścią z tego jest to, że w momencie przywołania funkcji otrzymasz błędy związane z ewaluacją. Wtedy podczas połączenia otrzymasz tylko błędy związane z funkcją.

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
To byłoby ryzykowne. string może mieć wszystko, a eval skończyłoby to ewaluacją bez żadnego rozważania.
iankit

4
Jasne, musisz pamiętać o kontekście, w którym go używasz, niezależnie od tego, czy będzie to właściwe, czy nie, biorąc pod uwagę ryzyko.
tvt173

1
Funkcja nie powinna być odpowiedzialna za sprawdzanie poprawności jej parametrów - to zadanie innej funkcji. Mówienie, że używanie eval z ciągiem znaków jest ryzykowne, oznacza, że ​​korzystanie z każdej funkcji jest ryzykowne.
red777

Nigdy nie należy używać, evalchyba że jest to absolutnie konieczne. getattr(__module__, method_name)jest znacznie lepszym wyborem w tym kontekście.
moi

14

nic z tego, co sugerowano, nie pomogło mi. Odkryłem to jednak.

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

Używam Pythona 2.66

Mam nadzieję że to pomoże


13
W jakim aspekcie jest to lepsze niż getattr ()?
V13

1
Dokładnie to, czego chciałem. Działa jak marzenie! Doskonały!! self.__getattribute__('title')jest równyself.title
ioaniatr

self.__getattribute__('title')w końcu nie działa (nie wiem dlaczego), ale func = getattr(self, 'title'); func();działa. Więc może lepiej jest użyć getattr()zamiast tego
ioaniatr

10
Czy ludzie, którzy nie znają Pythona, powinni przestać głosować na te śmieci? Użyj getattrzamiast tego.
Aran-Fey

6

Jako pytanie: Jak dynamicznie wywoływać metody w klasie za pomocą przypisania nazwy metody do zmiennej [duplikat] oznaczonej jako duplikat tak jak tutaj, zamieszczam pokrewną odpowiedź tutaj:

Scenariusz jest taki, że metoda w klasie chce dynamicznie wywoływać inną metodę w tej samej klasie. Dodałem kilka szczegółów do oryginalnego przykładu, który oferuje szerszy scenariusz i przejrzystość:

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

Dane wyjściowe (Python 3.7.x)

funkcja1: 12

funkcja2: 12


-7

To prosta odpowiedź, która pozwoli na przykład wyczyścić ekran. Poniżej znajdują się dwa przykłady, z eval i exec, które wypisują 0 na górze po czyszczeniu (jeśli używasz Windowsa, zmień clearna cls, użytkownicy Linuksa i Maca wychodzą tak jak na przykład) lub po prostu uruchom je odpowiednio.

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

3
Nie tego wymaga op.
Tuncay Göncüoğlu
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.