Zanurkuj w Pythonie/Funkcja getattr

Funkcja getattr

edytuj

Powinniśmy już wiedzieć, że w Pythonie funkcje są obiektami. Ponadto możemy dostać referencję do funkcji bez znajomości jej nazwy przed uruchomieniem programu. W tym celu podczas działania programu należy wykorzystać funkcję getattr.

Przykład. Funkcja getattr
>>> li = ["Larry", "Curly"]
>>> li.pop                                 #(1)
<built-in method pop of list object at 010DF884>
>>> getattr(li, "pop")                     #(2)
<built-in method pop of list object at 010DF884>
>>> getattr(li, "append")("Moe")           #(3)
>>> li
["Larry", "Curly", "Moe"]
>>> getattr({}, "clear")                   #(4)
<built-in method clear of dictionary object at 00F113D4>
>>> getattr((), "pop")                     #(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'pop'
  1. Dzięki temu dostaliśmy referencję do metody pop. Zauważmy, że w ten sposób nie wywołujemy metody pop; aby ją wywołać musielibyśmy wpisać polecenie li.pop(). Otrzymujemy referencję do tej metody. (Adres szesnastkowy wygląda inaczej na różnych komputerach, dlatego wyjścia będą się nieco różnić.)
  2. Operacja ta także zwróciła referencję do metody pop, lecz tym razem nazwa metody jest określona poprzez łańcuch znaków w argumencie funkcji getattr. getattr jest bardzo przydatną, wbudowaną funkcją, która zwraca pewien atrybut dowolnego obiektu. Tutaj wykorzystujemy obiekt, który jest listą, a atrybutem jest metoda pop.
  3. Dzięki temu przykładowi możemy zobaczyć, jaki duży potencjał kryje się w funkcji getattr. W tym przypadku zwracaną wartością getattr jest metoda (referencja do metody). Metodę tę możemy wykonać podobnie, jak byśmy bezpośrednio wywołali li.append("Moe"). Tym razem nie wywołujemy funkcji bezpośrednio, lecz określamy nazwę funkcji za pomocą łańcucha znaków.
  4. getattr bez problemu pracuje na słownikach
  5. Teoretycznie getattr powinien pracować na krotkach, jednak krotki nie posiadają żadnej metody, dlatego getattr spowoduje wystąpienie wyjątku związanego z brakiem atrybutu o podanej nazwie.

getattr na modułach

edytuj

getattr działa nie tylko na wbudowanych typach danych. Argumentem tej funkcji może być także moduł.

Przykład. Funkcja getattr w apihelper.py
>>> import odbchelper
>>> odbchelper.buildConnectionString             #(1)
<function buildConnectionString at 00D18DD4>
>>> getattr(odbchelper, "buildConnectionString") #(2)
<function buildConnectionString at 00D18DD4>
>>> object = odbchelper
>>> method = "buildConnectionString"
>>> getattr(object, method)                      #(3)
<function buildConnectionString at 00D18DD4>
>>> type(getattr(object, method))                #(4)
<type 'function'>
>>> import types
>>> type(getattr(object, method)) == types.FunctionType
True
>>> callable(getattr(object, method))            #(5)
True
  1. Polecenie to zwraca nam referencję do funkcji buildConnectionString z modułu odbchelper, który przeanalizowaliśmy w Rozdziale 2.
  2. Wykorzystując getattr, możemy dostać taką samą referencję, do tej samej funkcji. W ogólności getattr(obiekt, "atrybut") jest odpowiednikiem obiekt.atrybut. Jeśli obiekt jest modułem, atrybutem może być cokolwiek zdefiniowane w tym module np. funkcja, klasa czy zmienna globalna.
  3. Tę możliwość wykorzystaliśmy w funkcji info. Obiekt o nazwie object został przekazany jako argument do funkcji getattr, ponadto przekazaliśmy nazwę pewnej metody lub funkcji jako zmienną method.
  4. W tym przypadku zmienna method przechowuje nazwę funkcji, co można sprawdzić pobierając typ zwracanej wartości.
  5. Ponieważ zmienna method jest funkcją, więc można ją wywoływać. Zatem w wyniku wywołania callable otrzymaliśmy wartość True.

getattr jako funkcja pośrednicząca

edytuj

Funkcja getattr jest powszechnie używana jako funkcja pośrednicząca (ang. dispatcher). Na przykład mamy napisany program, który może wypisywać dane w różnych formatach (np. HTML i PS). Wówczas dla każdego formatu wyjścia możemy zdefiniować odpowiednią funkcję, a podczas wypisywania danych na wyjście getattr będzie nam pośredniczył między tymi funkcjami. Jeśli wydaje się to trochę zagmatwane, zaraz zobaczymy przykład.

Wyobraźmy sobie program, który potrafi wyświetlać statystyki strony w formacie HTML, XML i w czystym tekście. Wybór właściwego formatu może być określony w linii poleceń lub przechowywany w pliku konfiguracyjnym. Moduł statsout definiuje trzy funkcje -- output_html, output_xml i output_text, a wówczas program główny może zdefiniować pojedynczą funkcję, która wypisuje dane na wyjście:

Przykład. Pośredniczenie za pomocą getattr
import statsout

def output(data, format="text"):                                #(1)
    output_function = getattr(statsout, "output_%s" % format)   #(2)
    return output_function(data)                                #(3)
  1. Funkcja output wymaga jednego argumentu o nazwie data, który ma zawierać dane do wypisania na wyjście. Funkcja ta może także przyjąć jeden opcjonalny argument format, który określa format wyjścia. Gdy argument format nie zostanie określony, przyjmie on wartość "text", a funkcja się zakończy wywołując funkcję output_text, która wypisuje dane na wyjście w postaci czystego tekstu.
  2. Łańcuch znaków "output_" połączyliśmy z argumentem format, aby otrzymać nazwę funkcji. Następnie pobraliśmy funkcję o tej nazwie z modułu statsout. Dzięki temu w przyszłości będzie łatwiej rozszerzyć program nie zmieniając funkcji pośredniczącej, aby obsługiwał więcej wyjściowych formatów. W tym celu wystarczy dodać odpowiednią funkcję do statsout np. output_pdf i wywołujemy funkcję output podając argument format jako "pdf".
  3. Teraz możemy wywołać funkcję wyjściową w taki sam sposób jak inne funkcje. Zmienna output_function jest referencją do odpowiedniej funkcji w statsout.

Czy znaleźliśmy błąd w poprzednim przykładzie? Jest to bardzo niestabilne rozwiązanie, ponadto nie ma tu kontroli błędów. Co się stanie gdy użytkownik poda format, którego nie zdefiniowaliśmy w statsout? Funkcja getattr rzuci nam wyjątek związany z błędnym argumentem, czyli podaną nazwą funkcji, która nie istnieje w module statsout.

Na szczęście do funkcji getattr możemy podać trzeci, opcjonalny argument, czyli domyślnie zwracaną wartość, gdy podany atrybut nie istnieje.

Przykład. Domyślnie zwracana wartość w getattr
import statsout

def output(data, format="text"):
    output_function = getattr(statsout, "output_%s" % format, statsout.output_text)
    return output_function(data)        #(1)
  1. Ta funkcja już na pewno będzie działała poprawnie, ponieważ podaliśmy trzeci argument w wywołaniu funkcji getattr. Trzeci argument jest domyślną wartością, która zostanie zwrócona, gdy podany atrybut, czy metoda nie zostanie znaleziona.

Jak mogliśmy zauważyć, funkcja getattr jest niezwykle użyteczna. Jest ona sercem introspekcji. W następnych rozdziałach zobaczymy jeszcze więcej przydatnych przykładów.