Wpisy z kategorii: python

Team of experienced Django devs looking for remote contract work

Three month ago I decided to become a freelancer. As of 31 January 2011 I stopped working for Open-E company where I spent last 3 years as Python/Django developer.

Now I join forces together with my friend Bartosz Burclaf under name of eFabryka.com. We are a small, agile and goal oriented, distributed developers team operating out of Poland.

In our daily work we strongly believe in KISS and DRY principles. While designing and writing our code we follow TDD/BDD approach.

We are looking for interesting projects to dive into - find out more on: www.efabryka.com

Automatyczne sprawdzanie zgodności kodu Pythona z PEP8

Jakiś czas temu pisałem o skrypcie który sprawdza zgodność kodu Pythona z PEP8. Jednak podczas pracy nad kilkoma projektami zauważyłem, że nie chciało mi się za każdym razem ręcznie uruchamiać tego skryptu na całym projekcie. Sprowadzało się do tego że uruchamiałem go gdy projekt był już skończony. Prowadziło do tego że nie byłem w stanie czasowo wprowadzić wszystkich poprawek, bo było ich bardzo dużo.

Niby wydawało by się że wystarczy raz czy dwa przeczytać dokument PEP8 i przy pisaniu kodu stosować się do zaleceń, jednak potem gdy pisze się kod to się o wszystkim zapomina. Wydaje mi się że z pisaniem kodu jest jak z ładnym odręcznym pisaniem, to nawyk którego się nie da się nauczyć czytając jakieś poradniki. Tak samo jest ze stylem pisania kodu.

Skrypt do sprawdzania poprawności stylu kodu sprawdza się tutaj znakomicie, pozwala w szybki sposób na wyuczenie nawyku pisania kodu zgodnego z PEP8. Aby ułatwić sprawę, dodałem do testów danego projektu, przypadek testowy który uruchamia skrypt i sprawdza kod całego projektu. Jeżeli kod nie jest zgody z PEP8, to wyrzuca wyjątek i pokazuje dokładnie co trzeba poprawić.

from StringIO import StringIO

import sys

import unittest

import pep8


class PEP8TestCase(unittest.TestCase):

    def test_pep8_rules(self):  
        sys.argv[1:] = ['--filename=*.py', '--show-source', '--show-pep8', 
                               my_project.__path__[0]]  
        buf = StringIO()         
        sys.stdout = buf
        pep8._main()            
        sys.stdout = sys.__stdout__   
        result = buf.getvalue()

        self.assertEqual("", result,
                     "Code messages should be empty but was:\n" + result)

Wystarczy teraz na bieżąco podczas pisania kodu uruchamiać testy i mamy pewność że powstały kod jest zgodny z PEP8. Bardzo fajnie sprawdza się to także z pisaniem kodu wykorzystując Test Driven Development. Podczas pisania i uruchamiania testów od razu mamy sprawdzony cały powstały kod.

Po paru poprawkach pisanie kodu zgodnego z PEP8 wejdzie nam tak w krew że podczas pisania kodu od razu zacznie się pisać poprawny kod. Przydaje się tutaj włączenie w edytorze pokazywanie białych znaków aby od razu wykrywać miejsca gdzie znajdują się nadmiarowe białe znaki.

Zauważyłem także, że dbając o styl kodu, wykrywa się też miejsca które można zrefaktoryzować i uprościć.

Jak sprawdzać zgodność kodu Pythona z PEP8

Znalazłem ostatnio bardzo fajne narzędzie do sprawdzania czy kod Pythona jest zgodny z zalecanym stylem pisania kodu w Pythonie(PEP8). Jest to skrypt uruchamiany z konsoli który sprawdza czy wskazany moduł jest zgodny z PEP8.

Przykład użycia:

$ pep8.py --filename=*.py --show-source --show-pep8 /sciezka/do/projektu/
parts/djangopl/sitemaps.py:14:5: E301 expected 1 blank line, found 0
def changefreq(self, obj):
^
Separate top-level function and class definitions with two blank lines.

Method definitions inside a class are separated by a single blank line.

Extra blank lines may be used (sparingly) to separate groups of related
functions.  Blank lines may be omitted between a bunch of related
one-liners (e.g. a set of dummy implementations).

Use blank lines in functions, sparingly, to indicate logical sections.
...

parametry:

  • filename - wzorzec jakie pliki mają być brane pod uwagę
  • show-source - przy każdym błędzie będzie wypisany kawałek kodu i wskazane w którym miejscu jest błąd
  • show-pep8 - do każdego błędu będzie podane wyjaśnienie z dokumentu PEP8

Wynik działania prezentuje się bardzo ładnie i można szybko wychwycić gdzie jest błąd i zobaczyć dlaczego tak nie powinno się pisać kodu w Pythonie.

Znalazłem też opis jak zintegrować sprawdzanie zgodności z PEP8 z testami wykorzystywanymi w systemie typu continuous integration:

http://www.ajaxline.com/continuous-integration-in-django-project

Przydatne linki związane z PEP8:

Debugowanie kodu z ipdb

Na pewno każdy kto programuje w Pythonie, słyszał o ipythonie, ale czy każdy wie co to jest pdb lub ipdb ?

ipdb jest rozbudowaną wersją debuggera pdb o podobnych właściwościach co ipython w porównaniu do zwykłego interpretera python'a. Gdy uczyłem się programować w python'ie około dwa lata temu do debugowania używałem po prostu "print" co nie jest dobrym rozwiązaniem. Jakiś czas temu dowiedziałem się o debuggerze pdb. Jest to debugger który jest częścią biblioteki standardowej, który umożliwia przerwanie wykonywanego kodu python'a i przejście do sesji interaktywnej python'a. Gdy program zostanie zatrzymany przez debugger pdb, mamy możliwość podglądania zmiennych, modyfikacje itd, słowem wszystko co możemy zrobić w interpreterze python'a.

Używanie debuggera

W miejscu w którym chcemy aby debugger się zatrzymał i wszedł do sesji interaktywnej, wpisujemy:

import pdb  
pdb.set_trace()

Gdy program dojedzie do tego miejsca, wykonywanie dalszego kodu zostanie zatrzymane i uruchomiona zostanie interaktywna sesja interpretera python'a.

Komendy:

  • help (h) - drukuje listę dostępnych poleceń,
  • help NAZWA_POLECNIA - drukuje pomoc dla tego polecenia,
  • where (w) - pokazuje listę wywołań, strzałką będzie oznaczone miejsce w którym aktualnie się jest,
  • list (l) - pokazuje kod źródłowy aktualnie wykonywanego kodu oraz miejsce w którym zatrzymał się debugger,
  • down (d) - skok w dół na liście wywołań,
  • up (p) - skok w górę na liście wywołań,
  • step (s) - wykonanie linii na której zatrzymał się debugger oraz wejście do środka wykonywanego kodu,
  • next (n) - wykonanie linii oraz przejście do następnej linii bez wchodzenia do środka wykonywanego kodu,
  • continue (c) - kontynuacja wykonywania kodu do następnego zatrzymania (pdb.set_trace())

Więcej poleceń znajdziemy w dokumentacji pdb

Teraz cała magia zaczyna się gdy zamiast pdb użyjemy ipdb

Instalacja ipdb

Aby zainstalować ipdb, najprościej jest skorzystać z easy_install:

easy_install ipdb

Teraz w kodzie który chcemy zatrzymać wpisujemy:

import ipdb    
ipdb.set_trace()

I mamy interaktywną sesje w interpreterze ipython'a :)
Dzięki ipdb, w ciągu ostatnich miesięcy, wielokrotnie udało mi się znaleźć błędy w kodzie które normalnie były by bardzo trudne do wykrycia. Polecam każdemu, kto chce odczuwać przyjemność z znajdowania błędów, z ipdb staje się to bardzo przyjemne :)

Unittesty i przeładowywanie modułów Pythona

Natknąłem się ostatnio na problem podczas pisania testów do kodu który się zmienia za każdym przypadkiem testowym. Przykład takiego przypadku testowego:

class TestCase1(unittest.TestSuite):
    def setUp(self):
        crate_test_env(a=10, c=6)
    
    def test_method1(self):
        import mymodule
       
        self.assertEqual(mymodule.a, 10)
        self.assertEqual(mymodule.c, 6)
       
    def test_method2(self):
        import mymodule
       
        self.assertEqual(mymodule.a, 10)
        self.assertEqual(mymodule.c, 6)
   
class TestCase2(unittest.TestSuite):
    def setUp(self):     
        crate_test_env(a=2, b=9)
   
    def testMet1(self):
        import mymodule
        self.assertEqual(mymodule.a, 2)
        self.assertEqual(mymodule.b, 9)

W metodzie setUp każdego przypadku testowego tworze plik mymodule.py do którego wpisuje zmienne a, b, lub c. Problem jest tutaj taki że przy pierwszym utworzeniu pliku mymodule.py i jego zaimportowaniu moduł jest zapisany w pamięci i w kolejnym utworzeniu modułu i jego zaimportowaniu jest widoczny nie moduł który został utworzony w aktualnym przypadku testowym, tylko ten który został zaimportowany jako pierwszy. Przeładowanie modułu za pomocą reload(modul) nic nie daje. Znalazłem w internecie kod który przekompilowuje dany modół jednak nie zdało to egzaminu, ciągle widziany był pierwszy załadowany moduł. Potem znalazłem klase RollbackImporter na stronie projektu pyunit która podczas inicjalizacji zapamiętuje wszystkie moduły zaimportowane w danym momencie i na końcu usuwa z pamięci wszystkie moduły które zostały zaimportowane po zrobieniu kopii sys.modules.

Jednak użycie tej klasy w metodzie setUp przypadku testowego nie chciało działać. Długo zastanawiałem się jak odizolować uruchomienie pojedynczego testu od zaimportowanych modułów w danym momencie. Wymyśliłem że musiałbym napisać własny testrunner, zacząłem przeglądać kod źródłowy modułu unittest. Gdy testrunner uruchamia nasze testy:

unittest.TextTestRunner(verbosity=2).run(nasze_testy)

Wywoływana jest klasa TestSuite która posiada metodę run która uruchamia po kolei wszystkie przekazane testy. Wystarczy więc nadpisać metodę __call__ klasy TestSuite i tam wstawić przed wykonaniem każdego testu kod z modułu pyunit. Przykład klasy która dziedziczy po unittest.TestSuite i nadpisuje metodę __call__:

class MyTestSuite(unittest.TestSuite):
    def __call__(self, result):
        rollbackImporter = None
        for test in self._tests:
            if rollbackImporter:
                rollbackImporter.rollbackImports()
            rollbackImporter = RollbackImporter()
          
            if result.shouldStop:
                break
            test.run(result)
        return result

Nadpisana metoda __call__ różni się od oryginału tylko dodaniem kodu:

rollbackImporter = None
for test in self._tests
    if rollbackImporter:
        rollbackImporter.rollbackImports()
    rollbackImporter = RollbackImporter()

 

Programowalne importy w Pythonie

Ostatnio musiałem paru osobom pokazać jak zaimportować coś programowalnie w Pythonie. Umieszczam tutaj ten opis jak to zrobić, może komuś się przyda.

To zaimportuje cały pakiet moj.jakis.pakiet

nazwa_pakietu = 'moj.jakis.pakiet'
zaimportowanypakiet = __import__(nazwa_pakietu, globals(),locals(),[''])

potem mamy

zaimportowanypakiet.MojaJakasKlasa

można też to wstawić do zmiennej

definicja_klasy = getattr(zaimportowanypakiet, 'MojaJakasKlasa')

potem nasza "definicja_klasy" będzie mieć w sobie atrybuty i metody klasy

instancja_klasy = definicja_klasy(atrybut1=1, atrybut2=2)

Profilownie kodu w Pythonie oraz Django

Ostatnio chciałem sprofilować moją aplikacje w Django. Postanowieniem przyjrzeć się tej kwestii. W Pythonie od wersji 2.2 w bibliotece standardowej jest profiler kodu HotShot(High performance logging profiler) Jest on zamiennikiem istniejącego we wcześniejszych wersjach Pythona modułu Profile. Moduł HotShot jest interface'em do modułu _hotshot z C, dzięki temu temu jest dużo szybszy niż stary moduł Profile i daje bardziej precyzyjne wyniki.

Przykład profilowanie dowolnego kodu z pomocą modułu HotShot:

Profilowanie funkcji moja_funkcja:

import hotshot

def moja_funkcja():
    j = 0
    for i in range(1,10000):
        j += i
       
    return j

if __name__ == '__main__':
    prof = hotshot.Profile("mojprofil.prof")
    prof.run("moja_funkcja()")
    prof.close()

Metoda Profile stworzy plik w którym zostaną zapisane statystki profilowania. Można także pomiędzy dowolny kod wstawić gdzie ma zacząć sie profilowanie a gdzie skończyć:

import hotshot

prof = hotshot.Profile("mojprofil.prof")
prof.start()

# ...tutaj kod który ma zostać sprofilowany...

prof.stop()
prof.close()

Drukowanie wyników:

import hotshot.stats

stats = hotshot.stats.load("mojprofil.prof")
stats.strip_dirs()
stats.sort_stats('time', 'calls')
stats.print_stats(20)
2 function calls in 0.004 CPU seconds

   Ordered by: internal time, call count

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.004    0.004    0.004    0.004 profile_test.py:3(moja_funkcja)
        1    0.000    0.000    0.004    0.004 :1()
        0    0.000             0.000          profile:0(profiler)

W zwróconym wyniku mamy następujące parametry:

ncalls
Ilość wywołań funkcji/metody
tottime
Całkowity czas wykonań danej funkcji wykluczając inne pod funkcje.
percall
Czas wykonania pojedynczej funkcji.
cumtime
Całkowity czas wykonania wszystkich funkcji o tej samej nazwie łącznie z innymi pod funkcjami które wywołuje ta funkcja.
percall
Czas wykonania pojedynczej funkcji, wraz z pod funkcjami, które wywołuje ta funkcja.
filename:lineno(function)
nazwa pliku:nr_linii(funkcja)

Tutaj można znaleźć dekorator który umożliwia, oznaczenie danej funkcji czy metody aby była sprofilowana

Wygenerowane wyniki można także zobaczyć w postaci graficznej, potrzebny jest do tego program kcachegrind dostępny pod linux'em. Nie znalazłem jakiegoś odpowiednika pod Windows'a, podobno można go uruchomić pod Windows'em z pomocą Cygwina, ale nie sprawdzałem bo używam Ubuntu, zarówno w pracy jak i w domu. Aby zobaczyć graficzne wyniki profilowania trzeba je skonwertować do formatu obsługiwanego przez kcachegrind. Potrzebny do tego jest pakiet kcachegrind-converters. Po zainstalowaniu pakietu wykonujemy polecenie hotshot2calltree nasz_plik_z_profilem > cachegrind.out.01.

Jeżeli chodzi profilowanie Django to istnieje kilka możliwości. Django posiada hander django.core.handlers.profiler-hotshot który umożliwia profilowanie widoków gdy używa się mod_pythona, druga możliwość to uruchomienie django przez serwer WSGI. Dokładny opis można znaleźć tutaj. Najbardziej ciekawy i elegancki sposób jaki znalazłem to profilowanie django za pomocą middleware'a dostępnego tutaj. Wystarczy dopisać go na początek listy middlewre'ów naszej aplikacji w settings.py. Aby z profilować dowolny widok wystarczy dopisać na koniec url'a ?prof. Np. http://mojadomena.com/mojwidok/?prof. Po wywołaniu takiego widoku na ekranie otrzymamy statystyki profilowania.

O mnie

Nazywam się Dominik Szopa. Jestem programistą, entuzjastą języka Python oraz rozwiązań na nim opartych. W Pythonie programuje zarówno w pracy jak i w czasie wolnym. Język ten przypadł mi bardzo do gustu ponieważ jestem osobą która zawsze wszytko musi mieć robione perfekcyjnie, dzięki Pythonowi, programowanie stało się dla mnie czystą przyjemnością. Urzekła mnie prostota rozwiązań oraz czytelność kodu.

Pamiętam jak po raz pierwszy zetknąłem się z Pythonem w pracy. Miałem napisać prosty skrypt pokazujący wykorzystanie naszego API za pomocą protokołu SOAP. Wtedy Python był dla mnie językiem "egzotycznym", pierwszy raz wtedy usłyszałem tą nazwę, było to jakieś 1,5 roku temu. Najbardziej mnie zaskoczyło, jak się zapytałem kumpla jak oddzielać bloki kodu, zaskoczyła mnie odpowiedz "za pomocą wcięć". Wtedy wydało mi się to dziwne, ale po chwili zastanowienia stwierdziłem że to przecież genialne, wymusza na programiście, porządne pisanie kodu. Tak samo po co te wszystkie nawiasy klamrowe i średniki na końcu linii w innych językach, przecież i tak robie wcięcia to po co mi jeszcze to. Na studiach zaczynałem od takich języków jak: Pascal, potem Object Pascal(Delphi), C++, Java, PHP, nawet liznąłem trochę .NET'u.

Jednak żaden z tych języków nie dawał mi czystej przyjemności z programowania, pisałem w tym ponieważ albo musiałem(studia) albo nie znałem nic lepszego. Dopiero gdy odkryłem Pythona i mogłem w nim pisać zawodowo, programowanie stało się dla mnie czystą przyjemnością i pasją. Od chwili gdy zacząłem programować w Pythonie, wiem że w tym języku chce się specjalizować i w żadnym innym.

Około rok temu ze strony showmedo.com dowiedziałem się o framework'u do tworzenia aplikacji webowych Turbo Gears, zaciekawiło mnie to że jest coś takiego co ułatwia pisanie aplikacji webowych, jednak Turbo Gears nie przypadło mi do gustu z racji tego że było mieszanka innych narzędzi, framework'ów, na sieci było mnóstwo nie przychylnych opinii na ten temat. Potem dowiedziałem się o framework'u Django.

Przerobiłem tutorial i po prostu mnie oczarowało, urzekła mnie na ta prostota i łatwość z jaką się pisze aplikacje webowe w Django. Automatyczny panel administracyjny, ORM zamiast klepania sql'a, ładne mapowanie url'i, generowanie formularzy, wiele by tu można wymieniać. Pamiętam że w PHP napisanie jakieś większej aplikacji czy nawet takiego bloga jak ten z CMS'em to była porostu rzeźnia, już mnie mówiąc o samym PHP w porównaniu do Pythona, ale to już inna bajka. Jak zobaczyłem jak prosto w Django się pisze takie aplikacje webowe to stwierdziłem, to jest to czego mi było trzeba. Spodobało mi się profesjonalne podejście developerów Django, gdy dowiedziałem się że wszystko co jest w Django zostało napisane od zera. Jeszcze bardziej spodobało mi się to że Django jest napisane w Pythonie, wiec niejako "dziedziczy" zalety samego Pythona :)

Chciałbym tutaj pisać o różnych problemach i ciekawostkach z którymi spotykam się pisząc w Pythonie. Uważam że wiedza to dobro wspólne i powinno się nią dzielić.