render_to_response ... jak zrobić to wydajniej?

Zacznę od czegoś związanego z optymalizacją. Optymalizacja to ostatni etap rozwoju aplikacji, ale kto powiedział że zaczynamy :).

Standardowo do renderowania strony z szablonu używa się funkcji render_to_response. Jest ona prosta i wygodna w użyciu. Jednak nie jest ona optymalna. Za każdym razem pliki z szablonami są ładowane z dysku i parsowane, a dopiero następnie strona jest renderowana. Więc, aby przyśpieszyć proces renderowania, wystarczy zapamiętywać wczytane i sparsowane szablony do późniejszego użycia. Aby najpierw załadować plik z dysku i go sparsować wystarczy napisać:

from django.template import loader
temp = loader.get_template(template)

Ta funkcja zwraca nam klasę. Wystarczy wywołać funkcję render z tej klasy, aby wygenerować stronę z danym kontekstem (czyli listą asocjacyjną ze zmiennymi).

temp.render(context)

Gdzieś trzeba zapisać taki szablon. Najlepszym wydaje się być globalna lista asocjacyjna. Używanie w tym przypadku pamięci cache, może być nieoptymalne, a nawet kłopotliwe. Zwrócona klasa z szablonem to nie jest jedna klasa, a całe drzewo ze wszystkimi elementami które są w szablonie. Serializacja takiej ogromnej struktury danych na pewno nie przebiega zbyt szybko. Używanie globalnej zmiennej nie jest w tym przypadku niebezpieczne, nawet w wielowątkowej aplikacji, ponieważ zapis do niej robimy tylko raz, przy pierwszym wywołaniu szablonu. Potem idą tylko odczyty.

No to zobaczmy jak wygląda moja funkcja z cachem:

templates = {}

def django_template_to_string( request, variables, template ):
    if not DEVEL:
        temp = templates.get( template )
        if temp is None:
            temp = loader.get_template(template)
            templates[template] = temp
    else:
        temp = loader.get_template(template)

    c = RequestContext(request)
    c.update(variables)
    return temp.render(c)]

Należy pamiętać, że teraz szablony są trzymane w pamięci, co jest niewygodne podczas rozwijania aplikacji. Jeżeli zmienimy coś w pliku, to nie zostanie to zauważone, bo to nie jest plik kodu Pythona tylko szablon. Dlatego też pojawiła się tam zmienna DEVEL. Należy ją ustawić aby było tak jak poprzednio. Funkcja ta z ustawioną tą zmienną, działa prawie identycznie co stare dobre render_to_response.

Dla ułatwienia pracy warto jest użyć dekorator, aby wszystko się działo automatycznie. Jest to o tyle wygodne, że nie musimy używać żadnych dodatkowych funkcji, które pojawiały się przy render_to_response. W przypadku dekoratora dzieje się to w nim. Użycie go jest bardziej elastyczne, więc gorąco go polecam.

decorator_with_args = lambda decorator: lambda *args, **kwargs:
                lambda func: decorator(func, *args, **kwargs )

@decorator_with_args
def django_template(func, template=None):
    def wrapper(request,*args,**kwargs):
        variables = func(request, *args, **kwargs )
        string = django_template_to_string( request, variables, template )
        return HttpResponse( string )

    wrapper.__name__ = func.__name__
    wrapper.__dict__ = func.__dict__
    wrapper.__doc__ = func.__doc__
    return wrapper</code>

Teraz użycie jest prostsze niż domyślnie, zobaczmy:

@django_template("moja_strona.html")
def master_home(request):
    variables = { 'title'   : "Hello World!" }
    return variables

Żadnego render_to_response. Ok, zobaczmy jakie jest przyśpieszenie dla dość skomplikowanej strony:

render_to_response: Time per request:       223ms
django_template.py: Time per request:       101ms

Jak widać jest to bardzo znacząca różnica.To są moje wyniki dla mojej strony. Pełny kod źródłowy: django_template.py


Komentarze do notki “render_to_response ... jak zrobić to wydajniej?”

  1. amz 

    Jak kolorujecie kod :P?

  2. Zal 

    " Ta funkcja zwraca nam klasę. "

    Tak z ciekawości - nie powinno być "ta metoda zwraca nam obiekt (ew. obiekt klasy)"?

  3. kaliber 

    Pewnie kolorują VIM-em i konwertują do HTML.

  4. Oinopion 

    Można też zaprządz Django do tego: <a href="http://www.djangoproject.com/documentation/cache/">Django’s cache framework</a>

  5. fredd4 

    Kolorujemy popularną biblioteką do pythona o nazwie pygmentize. Potrafi ona wygenerować kod HTML dla dowolnego języka i zwrócić osobno style CSS.

    A w Django można używać cache, ale o doświadczeniach z tym jeszcze napiszemy :). Do tego przypadku nie warto tego używać, bo serializacja wygenerowanych obiektów zajęła by sporo czasu, niepotrzebnie zresztą. Tak jest łatwiej, wygodniej i szybciej :)

  6. marcov 

    Ciekawy sposób na przyspieszenie pracy, jednak zignorowanie tego, co zwraca View niesie za sobą pewien problem, mianowicie np. HttpResponseRedirect czy HttpResponse wewnątrz widoku zostaje zignorowany i zawsze (nawet jeśli View niczego nie zwraca) jest renderowany podany w argumencie dekoratora szablon. Jak np. w tym momencie przekierować user'a na inną stronę po pomyślnym zalogowaniu?

  7. fredd4 

    To nie jest trudne. W dekoratorze wystarczy sprawdzić czy zwrócona wartość to jest słownik, czy też jest to instancja obiektu HttpResponse. Dzięki temu będziemy mogli używać tego w standardowy sposób.

    Coś w stylu:

    variables = func(request, *args, **kwargs )
    if isinstance( variables, HttpResponse):
        return variables
    else:
        ...


    Lub też w tychże miejscach możemy zrezygnować z używania dekoratora i robić to po staremu. Użycie tego sposobu nie ogranicza nas :)

  8. marcov 

    Faktycznie działa - i faktycznie proste. Dzięki za megaszybką odpowiedź ;-)

  9. g00fy 

    fajnie by było gdyby można było to podpiąć pod generic views. moze da sie to jakos zrobic bez ingerowania w django?

  10. Żalownik 

    też tak bym chciał ;]

Zostaw odpowiedź