Zanurkuj w Pythonie/Potęga introspekcji - wszystko razem

Wszystko razem

edytuj

Ostatnia linia kodu, jedyna której jeszcze nie rozpracowaliśmy, to ta, która odwala całą robotę. Teraz umieszczamy wszystkie puzzle w jednym miejscu i nadchodzi czas, aby je ułożyć.

To jest najważniejsza część apihelper.py:

    print "\n".join(["%s %s" %
                   (method.ljust(spacing),
                    processFunc(unicode(getattr(object, method).__doc__)))
                   for method in methodList])

Zauważmy, że jest to tylko jedno wyrażenie podzielone na wiele linii, ale które nie używa znaku kontynuacji (znaku odwrotnego ukośnika, \). Pamiętasz jak powiedzieliśmy, że pewne instrukcje mogą być rozdzielone na kilka linii bez używania odwrotnego ukośnika? Wyrażenia listowe są jednym z tego typu wyrażeń, ponieważ całe wyrażenie jest zawarte w nawiasach kwadratowych.

Zacznijmy od końca i posuwajmy się w tył. Wyrażenie

for method in methodList

okazuje się być wyrażeniem listowym. Jak wiemy, methodList jest listą wszystkich metod danego obiektu, które nas interesują, więc za pomocą tej pętli przechodzimy tę listę wykorzystując zmienną method.

Przykład. Dynamiczne pobieranie notki dokumentacyjnej
>>> import odbchelper
>>> object = odbchelper                    #(1)
>>> method = 'buildConnectionString'       #(2)
>>> getattr(object, method)                #(3)
<function buildConnectionString at 010D6D74>
>>> print getattr(object, method).__doc__  #(4)
Tworzy łańcuchów znaków na podstawie słownika parametrów.

   Zwraca łańcuch znaków.
  1. W funkcji info, object jest obiektem do którego otrzymujemy pomoc, a ten obiekt zostaje przekazany jako argument.
  2. Podczas iterowania listy methodList, method jest nazwą aktualnej metody.
  3. Używając funkcji getattr otrzymujemy referencję do funkcji method z modułu object.
  4. Teraz wypisanie notki dokumentacyjnej będzie bardzo proste.

Następnym elementem puzzli jest użycie unicode na notce dokumentacyjnej. Jak sobie przypominamy, unicode jest wbudowaną funkcją, która przekształca dane na unikod, ale notka dokumentacyjna jest zawsze łańcuchem znaków, więc po co ją jeszcze konwertować na unikod? Nie każda funkcja posiada notkę dokumentacyjną, a jeśli ona nie istnieje, atrybut __doc__ ma wartość None.

Przykład. Po co używać unicode na notkach dokumentacyjnych?
>>> def foo(): print 2
>>> foo()
2
>>> foo.__doc__     #(1)
>>> foo.__doc__ == None #(2)
True
>>> unicode(foo.__doc__)    #(3)
u'None'
  1. Możemy łatwo zdefiniować funkcję, która nie posiada notki dokumentacyjnej, tak więc jej atrybut __doc__ ma wartość None. Dezorientujące jest to, że jeśli bezpośrednio odwołamy się do atrybutu __doc__, IDE w ogóle nic nie wypisze. Jednak jeśli się trochę zastanowimy nad tym, takie zachowanie IDE ma jednak pewien sens[1]
  2. Możemy sprawdzić, że wartość atrybutu __doc__ aktualnie wynosi None przez porównanie jej bezpośrednio z tą wartością.
  3. W tym przypadku funkcja unicode przyjmuje pustą wartość, None i zwraca jej unikodową reprezentację, czyli 'None'.
  4. li.append.__doc__ jest łańcuchem znaków. Zauważmy, że wszystkie angielskie notki dokumentacyjne Pythona korzystają ze znaków ASCII, dlatego możemy spokojnie je przekonwertować do unikodu za pomocą funkcji unicode.

Teraz kiedy już mamy pewność, że otrzymamy unikod, możemy przekazać otrzymany unikodowy napis do processFunc, którą już zdefiniowaliśmy jako funkcję zwijającą lub niezwijającą białe znaki (w zależności od przekazanego argumentu). Czy już wiemy, dlaczego wykorzystaliśmy unicode? Do przekonwertowania wartości None na reprezentację w postaci unikodowego łańcucha znaków. processFunc przyjmuje argument będący unikodem i wywołuje jego metodę split. Nie zadziałałoby to, gdybyśmy przekazali samo None, ponieważ None nie posiada metody o nazwie split i rzucony zostałby wyjątek. Może się zastanawiasz, dlaczego nie konwertujemy do str? Ponieważ tworzone przez nas notki są napisami unikodowymi, w których nie wszystkie znaki należą do ASCII, a zatem str rzuciłby wyjątek.

Idąc wstecz, widzimy, że ponownie używamy formatowania łańcucha znaków, aby połączyć wartości zwrócone przez processFunc i przez metodę ljust. Jest to metoda łańcucha znaków (dodajmy, że napis unikodowy także jest łańcuchem znaków, tylko nieco o większych możliwościach), której jeszcze nie poznaliśmy.

Przykład. Poznajemy ljust
>>> s = 'buildConnectionString'
>>> s.ljust(30) #(1)
'buildConnectionString         '
>>> s.ljust(20) #(2)
'buildConnectionString'
  1. ljust wypełnia napis spacjami do zadanej długości. Z tej możliwości korzysta funkcja info, aby stworzyć dwie kolumny na wyjściu i aby wszystkie notki dokumentacyjne umieścić w drugiej kolumnie.
  2. Jeśli podana długość jest mniejsza niż długość napisu, ljust zwróci po prostu napis niezmieniony. Metoda ta nigdy nie obcina łańcucha znaków.

Już prawie skończyliśmy. Mając nazwę metody method uzupełnioną spacjami poprzez ljust i (prawdopodobnie zwiniętą) notkę dokumentacyjną otrzymaną z wywołania processFunc, łączymy je i otrzymujemy pojedynczy napis, łańcuch znaków. Ponieważ odwzorowujemy listę methodList, dostajemy listę złożoną z takich łańcuchów znaków. Używając metody join z napisu "\n", łączymy tę listę w jeden łańcuch znaków, gdzie każdy elementem listy znajduje się w oddzielnej linii i ostatecznie wypisujemy rezultat.

Przykład. Wypisywanie listy
>>> li = ['a', 'b', 'c']
>>> print "\n".join(li)  #(1)
a
b
c
  1. Ta sztuczka może być pomocna do znajdowania błędów, gdy pracujemy na listach, a w Pythonie zawsze pracujemy na listach.

I to już był ostatni element puzzli. Teraz powinieneś zrozumieć ten kod.

    print "\n".join(["%s %s" %
                   (method.ljust(spacing),
                    processFunc(unicode(getattr(object, method).__doc__)))
                   for method in methodList])
  1. Pamiętamy, że każda funkcja zwraca pewną wartość? Jeśli funkcja nie wykorzystuje instrukcji return, zostaje zwrócone None, a częste wyświetlanie None po wykonaniu pewnych funkcji przez IDE Pythona mogłoby być trochę uciążliwe.