Posted by Jarosław Zabiełło
Mon, 01 Jan 2007 18:36:00 GMT
Korzystając z tego, że niedawno do repozytorium Rubiego został dodany YARV (wirtualna maszyna Rubiego) pokusiłem się o małe porównanie wydajności. Jako test użyłem prostego programu jaki pojawił się na liście pl.comp.lang.pyhon (musiałem tylko trochę zwiększyć pętle do 10 milionów iteracji, aby były widoczne jakieś wyniki) Użyty sprzęt: Athlon64 3700+, 64bit Ubuntu 6.0.6.
Sprawdziłem też z ciekawości PHP 5.2 na tym samym sprzęcie. Dla tak prostego testu powinien móc dać z siebie wszystko. Jest jednak wolniejszy od Rubiego 1.9.
Kod dla Pythona:
import time
t=time.time()
a=0
for i in xrange(10000000):
a += i
print a
print time.time() - t
Kod dla Lua:
t = os.clock()
a = 0
for i = 1,10000000 do
a=a+i
end
print(a)
print(os.clock() - t)
Kod dla Rubiego:
t = Time.now
a = 0
for i in 1..10_000_000
a = a+i
end
puts a
puts Time.now - t
Kod dla PHP:
<?php
function microtime_float() {
$time = microtime();
return (double)substr($time, 11) + (double)substr($time, 0, 8);
}
$t = microtime_float();
$a = 0;
for ($i = 0; $i < 10000000; $i++) {
$a += $i;
}
print "$a\n";
print microtime_float()- $t;
?>
Wyniki:
- Lua: 1.14 s
- Ruby 1.9: 1.49 s
- PHP 5.2: 1.74 s
- Python 2.5: 2.64 s
- Python 2.4.3: 2.72 s
- Ruby 1.8.5: 3.83 s.
Drugie podejście
Tym razem wynik uśredniam dla 10 prób. Zwiększyłem ilość iteracji do 20 mln. No i… Ruby 1.9 pobił wszystko. Zarówno Pythona jak i Lua! (PHP 5.2 po 30 sek. zgłosił timeout. Musiałem więc uśrednić po 5 wynikach.)
PYTHON
import time
def bench():
a, t = 0, time.time()
for i in xrange(20000000):
a+=i
return time.time()-t
result = 0
for i in range(10):
result += bench()
print result / 10.0
RUBY
def bench
a, t = 0, Time.now
20_000_000.times { |i| a += i }
Time.now - t
end
result = 0
10.times { |i| result += bench }
puts result / 10
LUA
function bench()
a = 0
t = os.time()
for i = 1, 20000000 do
a = a + i
end
return os.time() - t
end
result = 0
for x = 1,10 do
result = result + bench()
end
print(result/10)
PHP
<?php
function bench() {
$a = 0;
$t = time();
for ($i = 0; $i < 20000000; $i++) $a += $i;
return time() - $t;
}
$result = 0;
for ($x = 1; $x <= 5; $x++) $result += bench();
print $result / 5.0;
?>
Wyniki
- Ruby 1.9: 2.0953268 s.
- Lua 5.1: 2.4 s.
- Python 2.5: 2.49481592178 s.
- Python 2.4.3: 2.71687791348 s.
- PHP 5.2: 4.2 s.
- Ruby 1.8.5: 9.0153751 s.
Jestem ciekaw kiedy wyjdzie Rails dostosowany do Ruby 1.9. Zapowiada się bardzo obiecująco!
Posted in PHP, Python, Ruby | Tags benchmark, lua, php, python, ruby, yarv | 19 comments
Posted by Jarosław Zabiełło
Tue, 15 Aug 2006 23:28:00 GMT
[vide: English version]
MySQLdb to główna biblioteka dla Pythona obsługująca bazę MySQL. Baza ta (od werseji 4.1 wzwyż) ma wbudowany wygodny mechanizm translacji znaków jakie mają być wyświetlane dla klienta. Oczywiście, należy założyć, że natywnym formatem danych dla bazy to UTF-8. Za pomocą prostej kwerendy (SET NAMES latin2) można wymusić aby wszelkie napisy były wypluwane do klienta w wybranym formacie (tu: iso-8859-2).
Ostatnio, przy okazji pracy z Pylons, przyjrzałem się bardzo ciekawemu projektowi: SQLAlchemy. Wszystko wskazuje na to, że SQLObject odejdzie do lamusa (tym bardziej że jego twórca coś go porzucił i planuje stworzyć nowy SQLObject2). SQLAlchemy jest nie tylko znacznie poteżniejszym ORM, ale ma także znacznie lepszą dokumentację niż SQLObject – ok. 117 stron samego tylko manuala.
Jedyny problem, jakie to typowe, to brak czytelnej opcji translacji polskich znaków. Podobny problem swego czasu znalazłem w Django (wysłałem patcha i to poprawili dodając na sztywno “SET NAMES utf8”) W wypadku SQLAlchemy nie ma opcji do definicji kodowania po stronie klienta. Manual wspomina tylko o ustawieniu kodowania dla bazy, a to nie to samo. Okazuje, się, że rozwiązanie jest bardzo proste. Podczas tworzenia połączenia do bazy wystarczy użyć dodatkowych parametrów. Nie ma potrzeby bawienia się w żadne kwerendy “SET NAMES”. Owe parametry to use_unicode i charset:
import MySQLdb
conn = MySQLdb.connect(
user='root',
passwd='',
db='test',
use_unicode=False,
charset='cp1250')
W powyższym wypadku, MySQL będzie zakładał że klient ma odebrać teksty w formacie stringów cp1250. Zaś w wypadku użycia “use_unicode=True” zwracane będą śliczne obiekty unicodowe! Przykładowy plik konfiguracyjny dla Pylonsa korzystającego z SQLAlchemy (config/init.py) może zatem wyglądać np. tak:
from sqlalchemy import *
import sqlalchemy.pool as pool
import MySQLdb
def getconn():
return MySQLdb.connect(
user='root',
passwd='',
db='test',
use_unicode=False,
charset='utf8')
db = create_engine(
'mysql://root:@localhost/test',
pool=pool.QueuePool(getconn, pool_size=20, max_overflow=40),
strategy='threadlocal')
metadata = BoundMetaData(db)
test_table = Table('test', metadata, autoload=True)
class Test(object):
def __str__(self):
return self.title
test_mapper = mapper(Test, test_table)
W powyższym przykładzie, wymusiłem kodowanie utf8 dla klienta oraz połączenie z bazą działać będzie w puli 20-40 wątków. Właśnie tego mi brakuje w Django: pracy wielowątkowej, bo zużywa ona mniej pamięci. W/w model można użyć w kontrolerze Pylons (controllers/home.py) np. tak:
from myproject.lib.base import *
from myproject.models import *
class HomeController(BaseController):
def index(self):
c.rows = select([test_table.c.id, test_table.c.name]).execute()
return render_response('/home.myt')
Posted in Databases, Pylons, Python | Tags mysql, pylons, python, sqlalchemy | 4 comments
Posted by Jarosław Zabiełło
Mon, 12 Jun 2006 20:29:00 GMT
W części pierwszej i drugiej opisałem podstawowe zalety Pythona i Django. Pora ma malutką demonstrację działającej aplikacji. Będzie to mała, ale poręczna wyszukiwarka biblijna (choć kod można łatwo zaadoptować do innych treści rzecz jasna). Jako bazę użyję MySQL5. Zakładam także, że docelowo projekt będzie chodził pod Linuksem. Natomiast będzie budowany i testowany pod Windowsami. Taki model pracy jest często spotykany. Django oczywiście w wersji 0.95 SVN, czyli wersji pozbawionej wcześniejszej “magii” (kod Django jest teraz bardziej jawny i oparty na standardowych mechanizmach Pythona)
Zaczynamy!
Na początku musimy stworzyć projekt oraz aplikację (Django w ramach jednego projektu potrafi obsługiwać wiele aplikacji)
admin-django startptoject myproject
cd myproject
manage.py startapp biblia
Zauważmy, że skrypt admin-django uruchamiamy tylko raz. Potem już posługujemy się skryptem manage.py. Dzięki temu odpada nam kombinowanie z definiowaniem zmiennej systemowej DJANGO_SETTINGS_MODULE. Listę wszystkich dostępny opcji, zarówno dla django-admin.py jak i manage.py, uzyskamy uruchamiając te skrypty bez żadnego parametru.
Wstępna konfiguracja.
Mamy zatem stworzony katalog z projektem i aplikacją. Wpierw trzeba skonfigurować plik settings.py
2. Na początku pliku ustawiamy:
import sys
if sys.platform == 'win32':
DEBUG = True
else:
DEBUG = False
TEMPLATE_DEBUG = DEBUG
ADMINS = (
('Administrator', 'twoj@email'),
)
MANAGERS = ADMINS
DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'myproject'
if sys.platform == 'win32':
DATABASE_USER = 'root'
DATABASE_PASSWORD = ''
else:
DATABASE_USER = 'user_na_linuksie'
DATABASE_PASSWORD = 'haslo_pod_linuksem'
if sys.platform == 'win32':
DATABASE_HOST = ''
else:
DATABASE_HOST = '/var/run/mysqld/mysqld.sock'
DATABASE_PORT = ''
ENABLE_PSYCO = False
Kilka istotnych uwag.
- Adres mailowy admina nie jest taki zupełnie nieistotny. W wypadku kiedy aplikacja na serwerze produkcyjnym się wyłoży na jakimś wyjątku, na ten adres jest automatycznie wysyłany mail z wszystkimi szczegółami. Trudno śledzić, czy w którymś momencie aplikacja się nie wywali. Django zadba, aby żaden taki wypadek nie umknął naszej uwadze1.
- Należy sobie ustawić odpowiednie hasła dostępu do MySQL5 pod windozą i pod linuksem.
- Ważne jest, aby wyłączyć obsługę akceleratora Psycho. To jeszcze nie jest dobrze przetestowane. Jak to miałem włączone, to pod Linuksem miałem problemy z działaniem djangowego panelu admina.
- Musimy ręcznie sobie stworzyć (jeśli tego nie zrobiliśmy) bazę. Django za nas bazy nie stworzy. W naszym przypadku, tabelkę na której będziemy pracować mam już gotową w bazie. Tu jest jej spakowany dump który trzeba sobie załadować.
Pozostałe ustawienia:
LANGUAGE_CODE = 'pl'
if sys.platform == 'win32':
MEDIA_ROOT = r'H:/home/myproject/biblia/public'
else:
MEDIA_ROOT = r'/home/myproject/biblia/public'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'myproject.biblia',
)
Do słownika INSTALLED_APPS musimy dodać naszą aplikację aby można było jej używać. Mówi o tym ostatnia linijka powyższego kodu. Zmienna MEDIA_ROOT musi być bezwzględną ścieżką do plików statycznych. Pod serwerem www odpowiada to zmiennej DOCUMENT_ROOT, czyli miejsca od którego serwer www cokolwiek widzi.
Interaktywne testowanie Django z poziomu konsoli.
W każdej chwili możemy odpalić interaktywny dostęp do środowiska Django za pomocą komendy:
Warto sobie wcześniej zainstalować
ipythona, bo Django domyślnie próbuje uruchomić jego zamiast standardowy interpreter. Dzięki ipythonowi mamy wspaniałe uzupełnianie metod do obiektów + historia wczesniejszych operacji, która nie znika wraz z zamknięciem
ipythona.
Serwer.
Możemy odpalić wbudowany serwer www za pomocą komendy:
Domyślnie serwer się podniesie pod adresem http://127.0.0.1:8000. Można zmienić zarówno port jak i adres jak ktoś chce, rzecz jasna. Odpal manage.py (bez parametrów) to się dowiesz, jak.
ORM
No dobrze, pora na coś bardziej interesującego. Django operuje na relacyjnej bazie danych za pomocą swojego ORM (mapera relacyjno-obiektowego) Zalet takiego podejścia jest wiele. Wspomnę tylko o tym, obiektowo można znacznie lepiej opisać dane modelu biznesowego niż to zrobić może czysty SQL ze swoimi kluczami obcymi i trigerami. Django wymaga aby w pliku myproject/biblia/models.py zdefiniować dane dla ORM’a. Zasada jest prosta: klasa odpowiada tabeli, a atrybuty klasy – jej polom. Aby jednak sobie uprościć życie, można posłużyć się skryptem manage.py.
Poniższy skrypt robi introspekcję bazy i wygeneruje pythonowe definicje wszystkich jej tabel. Jako parametr podajemy nazwę naszej aplikacji (mówiłem że Django jest zbudowane do pracy z wieloma aplikacjami w ramach projektu) Możemy spokojnie zamazać plik models.py bo po stworzeniu projektu nic tam szczególnego nie ma.
H:\home\myproject>manage.py inspectdb biblia > biblia/models.py
Mamy zatem naszą wstępną obiektową definicję dla nasej tabelki:
from django.db import models
class BibliaGdanska(models.Model):
id = models.IntegerField(primary_key=True)
ref = models.CharField(unique=True, maxlength=9)
chapter_nr = models.IntegerField(unique=True)
verse_nr = models.IntegerField(unique=True)
verse = models.TextField()
class Meta:
db_table = 'biblia_gdanska'
Może sprawdźmy, czy to działa za pomocą interaktywnej konsoli:
H:\home\myproject>manage.py shell
Deleting alias <dir>, it's a Python keyword or builtin.
Python 2.4.3 (#69, Apr 11 2006, 15:32:42) [MSC v.1310 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.
IPython 0.7.1.fix1 -- An enhanced Interactive Python.
? -> Introduction to IPython's features.
%magic -> Information about IPython's 'magic' % functions.
help -> Python's own help system.
object? -> Details about 'object'. ?object also works, ?? prints more.
In [1]: from myproject.biblia.models import BibliaGdanska
In [2]: BibliaGdanska.objects.count()
Out[2]: 31151L
Bingo! Widać, że działa.
Kontroler i szablon.
Teraz pora na naszą aplikację internetową. Musimy stworzyć kontroler. Z tajemniczych powodów developerzy Django zamiast MVC (model-view-controller) stosują nazwę MTV (model-template-view). W każdym razie, to co zwykle nazywamy kontrolerem oni nazywają widokiem (view) a to co nazywamy widokiem, oni nazywają szablonem. Mniejsza o nazwy. Kontroler tworzymy w pliku myproject/biblia/views.py
from django.shortcuts import render_to_response
def home(request):
return render_to_response('home.html')
Plik home.html jest szablonem. Stwórzmy na razie ten plik z napisem “alama kota” w środku, aby zobaczyć, czy to działa. Plik powinien leżeć w myproject/biblia/templates/home.html.
Rozwiązywanie adresów URL
Wpierw musimy powiedzieć Django aby wiedział, gdzie ma szukać naszego kontrolera. W tym celu dodajmy taką treść do pliku myproject/biblia/urls.py
from django.conf.urls.defaults import *
from settings import DEBUG, MEDIA_ROOT
urlpatterns = patterns('',
(r'', include('myproject.biblia.urls')),
)
if DEBUG:
urlpatterns += patterns('',
(r'^images/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/images', 'show_indexes': True}),
(r'^stylesheets/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/stylesheets', 'show_indexes': True}),
(r'^javascripts/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT+'/javascripts', 'show_indexes': True}),
)
Powyższy kod robi kilka rzeczy.
- Wiąże domyślną stronę z naszym projektem. Wejście na http://127.0.0.1:8000/ przekaże sterowanie do pliku myproject/biblia/urls.py (który za chwilkę stworzymy).
- Takie podejście powoduje że każda aplikacja może posiadać swoje niezależne zasady rozwiązywania adresu url. Dlatego oddelegowujemy obsługę URL’i do pliku urls.py wewnątrz naszej aplikacji.
- Zakomentowaną linijkę z panelem admina na razie zostawmy, wrócę do tego później.
- Ostatnie linijki są potrzebne do tego, aby Django obsługiwał nie tylko plik Pythona ale także obrazki, style kaskadowe i skrypty języka JavaScript. Przyjąłem (wzorując się na Railsach) że są one odpowiednio w folderach myproject/biblia/public/images, myproject/biblia/public/stylesheets i myproject/biblia/public/javascripts.
- Dlaczego ten kod jest dostępny tylko dla DEBUG=True? Ano dlatego, że do pracy developerskiej pod windozą nie potrzebujemy żadnego Apache’a ani Lighttpd. Wystarczy wbudowany serwer www jaki dostarcza Django. Natomiast na serwerze produkcyjnym, gdzie chcemy uzyskać największą wydajność, lepiej aby te pliki podawał serwer www i Django się tego nie dotykało. Tak będzie najlepiej i najszybciej.
Pozostał do skonfigurowania plik myproject/biblia/urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('myproject.biblia.views',
(r'^$', 'home'),
)
Tu również mamy zakomentowany panel admina. Zostawmy go na razie w spokoju. Można uruchamiać: http://127.0.0.1:8000/ powinno wyświetlić nasz szablon z treścią “ala ma kota”. No to może lepiej aby wyświetlić coś bardziej atrakcyjnego. Zbudujmy formularz.
Formularz
Formularze są zmorą większości programistów. Są upierdliwe w obłudze, walidacji i co gorsze, ciągle z nimi mamy do czynienia. Aby ułatwić nam życie, Django stosuje specjalną technikę tzw. manipulatorów, aby praca z formularzami była miła i przyjemna. Muszę przyznać, że to była jedna z ważniejszych rzeczy, jaka mnie przyciągnęła do Django. Pylons i Rails mają swoje helpery, ale nie są one aż tak wygodne jak to, co oferuje Django3.
Zobaczmy jak się używa manipulatora. Wpierw zmieńmy nasz szablon (plik: myproject/biblia/templates/home.html). Będzie to prosty formularz z jednym polem gdzie wpiszemy wyszukiwane słowo.
<form method="get" action="">
{% if form.q.errors %}
<div class="formError">
Wpisz frazę o długości min. 3 znaków.
</div>
{% endif %}
{{ form.q }}
<input type="submit" value="szukaj" />
</form>
Manipulator.
Teraz pora na manipulator. Musimy stworzyć plik myproject/biblia/manipulators.py o przykładowej treści:
import re
from django import forms
from django.core import validators
class SzukajManipulator(forms.Manipulator):
def __init__(self):
regex = re.compile(r'(\w{3,})', re.U)
self.fields = (
forms.TextField(
field_name='q',
length=30,
maxlength=255,
is_required=True,
validator_list=[validators.MatchesRegularExpression(regex)]
),
)
Teraz zepnijmy wszystko razem w djangowym widoku (naszym kontrolerze). Zmieńmy treść pliku myproject/biblia/views.py na:
from django.shortcuts import render_to_response
from django import forms
from manipulators import SzukajManipulator
from models import BibliaGdanska
def home(request):
recordset = []
errors = _GET = {}
manipulator = SzukajManipulator()
_GET = request.GET.copy()
if request.GET:
errors = manipulator.get_validation_errors(_GET)
if not errors:
manipulator.do_html2python(_GET)
recordset = BibliaGdanska.objects.all().filter(verse__icontains=_GET['q'])
form = forms.FormWrapper(manipulator, _GET, errors)
return render_to_response(
'home.html',
{'request': request,
'form': form,
'recordset': recordset,
'hits': len(recordset)})
Wyświetlenie wyników.
Aplikacja działa4, ale nie widać wyników. Wyniki wyświetlimy w tym samym szablonie. Teraz będzie wyglądał tak:
<form method="get" action="">
{% if form.q.errors %}
<div class="formError">
Wpisz frazę o długości min. 3 znaków.
</div>
{% endif %}
{{ form.q }}
<input type="submit" value="szukaj" />
</form>
{% if recordset %}
<div>
Znaleziono <b>{{ hits }}</b> wersetów:
</div>
<ol>
{% for row in recordset %}
<li>
{{ row.ref}} {{ row.chapter_nr}}:{{ row.verse_nr }}
"{{ row.verse }}"
</li>
{% endfor %}
</ol>
{% else %}
Nie znaleziono wersetów dla frazy {{ request.GET.q }} :(
{% endif %}
Program powinien działać. Jednak w wypadku znalezienia mniej niż 5 wersetów niezbyt po polsku zabrzmi wynik, np. “Znaleziono 1 wersetów”. W wypadku języka angielskiego możemy zmodyfikować ten fragment szablonu za pomocą modyfikatora pluralize5. Polski jezyk jest jednak znacznie bardziej wyrafinowany od angielskiego, bo używa 2 różnych form dla liczby mnogiej: 1 werset, 2 wersety, 5 wersetów. Na szczęście możemy sobie zmienić sposób pracy tego modyfikatora.
Przeciążene modyfikatora
Aby nadpisać istniejący modyfikator lub dodać nowy, własny, należy stworzyć wpierw folder myproject/biblia/templatetags z pustym plikiem o nazwie init.py. Dodajemy tam drugi plik, o nazwie, powiedzmy: plugins.py o następującej treści:
import re, sys
from django import template
register = template.Library()
@register.filter(name='pluralize')
def pluralize(value):
"""Polish implementation"""
try:
if int(value) in (2,3,4):
return 'y'
elif int(value) >= 5:
return '\xc3\xb3w'
except ValueError:
pass
except TypeError:
try:
if int(value) in (2,3,4):
return 'y'
elif int(value) >= 5:
return '\xc3\xb3w'
except TypeError:
pass
return ''
Zamiast polskiego znaczka ó, użyłem zapisu ’\xc3\xb3’ bo to wartość utf-8 i taki zapis jest niezależny od tego, w czym otworzymy plik. Nikt przypadkowo nie popsuje nam polskich ogonków.
Aby to zadziałało, musimy gdzieś na początku w szablonie dodać linijkę, która załaduje nam nową definicję modyfikatora pluralize.
Kontekst i dziedziczenie szablonów.
Nasz kod działa, ale kod HTML jest daleki od doskonałości. Dobrze byłoby dodac jakiś nagłówek, stopkę itp. Django skopiowało z pythonowych szablonów Cheetah bardzo ciekawy sposób tworzenia kolejnych szablonów za pomocą obiektowego przeciążania starych.
Stwórzmy nasz szablon bazowy (myproject/biblia/templates/base.html)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Przykład aplikacji w Django</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h3>Wyszukiwarka Biblii Gdańskiej</h3>
<h4> (przykład aplikacji w Django)</h4>
<div id="main">
{% block main %}
tutaj będzie jakaś nowa treść
{% endblock %}
</div>
<hr size="1" />
<div>
© 2006
<a href="http://blog.zabiello.com">Jarosław Zabiełło</a>.
</div>
</div>
</body>
</html>
Ostatecznie szablon home.html będzie więc wyglądał tak:
{% extends "base.html" %}
{% load plugins %}
{% block main %}
<form method="get" action="">
{% if form.q.errors %}
<p class="formError">Wpisz frazę o długości min. 3 "znaków".</p>
{% endif %}
{{ form.q }}
<input type="submit" value="szukaj" />
</form>
{% if recordset %}
<div>Znaleziono <b>{{ hits }}</b> werset{{ hits|pluralize }}:</div>
<ol>
{% for row in recordset %}
<li>
{{ row.ref}} {{ row.chapter_nr}}:{{ row.verse_nr }}
"{{ row.verse }}"
</li>
{% endfor %}
</ol>
{% else %}
<p>Nie znaleziono wersetów dla frazy <b>{{ request.GET.q }}</b></p>
{% endif %}
{% endblock %}
Szablon oparty na innym szablonie implementuje dowolne bloki z poprzedniego. Cały pozostały kontekst jest automatycznie dziedziczony. Takie podejście bardzo skraca wielkość szablonów i czyni je jeszcze bardziej czytelnymi.
Podświetlanie szukanej frazy.
Do pliku plugins.py gdzie wcześniej zmieniliśmy definicję modyfikatora pluralize, dodajmy kolejny. Będzie nam podświetlał w wersetach to, co szukamy.
@register.filter(name='highlight')
def highlight(s, q):
if not isinstance(s, unicode):
s = unicode(s, 'utf-8')
s = re. compile('('+re.escape(q)+')', re.U|re.I).sub(r'<strong class="highlight">\1</strong>', s).encode('utf-8')
return s
W środku zastosowano sztuczkę z przełączeniem się na obiekty Unicode, aby mieć pewność że polskie ogonki duże i małe będą tak samo traktowane. W szablonie home.html wystarczy zmienić jedną linijkę:
{{ row.verse|highlight:request.GET.q }}
Co dalej?
Mam nadzieję, że artykuł przybliżył trochę praktyczną stronę uzywania Django. To dobry i szybki framework. Przykładowe serwisy jakie w nim napisano są znacznie bardziej skomplikowane i większe niż te, co widać na stronie Rails. Django został napisany do szybkiego tworzenia całkiem sporych serwisów
W kolejnym artykule, pokażę jak sobie wygodnie skonfigurować panel admina który udostępnia Django. Panel admina nie jest żadnym prostym rusztowaniem (scaffolding) do modyfikacji bazy. To kompletna aplikacja dla użytkownika końcowego. Bardzo wygodna i łatwa w użyciu. Panel Django jest kolejną z jego cech, która daje mu wyraźną przewagę w stosunku do konkurencji.
-
1 Kompletnie inaczej od 99% aplikacji pehapowych, gdzie o błędach dowiadujemy się najczęściej dopiero wtedy, jak wszystko leży, albo jak ktoś łaskawie nas poinformuje.
2 Jak widać, to normalny plik Pythona. Żadne chore XML i inne formaty nie są w ogóle nam potrzebne – Python jest wystarczająco dobry, aby łatwo i przyjemnie prezentować zagnieżdżone struktury. Inne języki, jak np. Java nie zbyt dobrze nadają się do takich rzeczy, dlatego muszą posiłkować się XML.
3 Tzn. Railsy się już poprawiły, bo jest plugin który dodaje im taką samą funkcjonalność jak Django. Nazywa się active-form i można go zainstalować prosto spod RadRails’a.
4 Kto nie wierzy, niech sobie wpisze print recordset po operacji wyszukiwania. Na konsoli serwera www wyświetli mu się zawartość tej zmiennej.
5 Szablony Django korzystają ze składni modyfikatorów podobnych do pehapowych Smartów.
Posted in Python, Django | Tags django | 7 comments
Posted by Jarosław Zabiełło
Sat, 27 May 2006 16:53:00 GMT
Jak wspomniałem w pierwszej części, Django jest frameworkiem który wyrósł na bazie zastosowań komercyjnych. Zanim twórcy zdecydowali się otworzyć i udostępnić jego kod na zasadach open-source, Django był już wcześniej intensywnie używany w zastosowaniach komercyjnych1.
Przewaga Django
Wcześniej pisałem o zaletach Pylonsów. Jednakże, po moich ostatnich testach z “oczyszczoną z magii” wersją Django muszę przyznać, że jest to chyba aktualnie najlepszy framework, z jakim miałem do czynienia w tej klasie zastosowań.
Django oferuje znacznie więcej helperów i udogodnień niż świetny Pylons. Poza wbudowaną autoryzacją i zaawansowanym panelem admina, Django pracuje na wyższym poziomie abstrakcji niż Pylons czy Rails. Zamiast prostych generatorów HTML dla formularzy, Django wprowadza kontrolki, które pracują na wyższym poziomie abstrakcji. Bardzo przejrzyście obsługuje także walidowanie formularzy. Kto się męczył z obsługa formularzy, nareszcie odetchnie z ulgą. Idea manipulatorów bardzo upraszcza pisanie obsługi formularzy. Ktoś powie, że to można sobie samemu napisać. Owszem. Ale Django daje ci to już gotowe. Uruchamiasz i używasz zamiast tracić czas.
Sprawa dokumentacji, i promocji w ogólności, w wypadku wiekszości frameworków Pythona jest po prostu żenująca. Zdumiewające jest jak to jest możliwe, że nawet tak potężne środowisko jakim jest Zope ma tak słabą stronę i chaotycznie porozrzucaną dokumentację.
Kilka mądrych posunięć.
Pierwszą, mądrą rzeczą, jaką zrobiono po otwarciu kodu Django, było skupienie się na pisaniu dokumentacji. Dzięki temu, Django ma dziś jedną z najlepszych dokumentacji pomiędzy pythonowymi frameworkami. Bez dobrej dokumentacji, trudno aby ktokolwiek się zainteresował projektem.
Dokumentacja Django nie równa się jeszcze temu co jest dostępne dla Railsów ale jest i tak lepsza od większości frameworków pythonowych.
Drugim, równie dobrym posunięciem, był refaktoring pierwotnego kodu zgodnie z zasadami filozofii Pythona. Wg zasady, ze działanie jawne jest lepsze od domyślnego, nastąpił proces usuwania “magii” z kodu. Np. teraz zamiast tajemniczych prefiksów do nazw dodatkowych metod modelu, można tworzyć je bardziej naturalnie – jako metody klasowe Pythona. Aktualnie wersja pozbawiona “magii” jest dostępna w repozytorium SVN. Frameworkiem, który jest mocno przeładowany “magicznymi” metodami jest Rails. Może to i ładnie wygląda, ale filozofia Python potępia takie podejście, gdyż to utrudnia programiście w szybkim zorientowaniu się o co chodzi w kodzie.
Trzecim, dobrym pomysłem, jest możliwości testowania środowiska w sposób interaktywny w interpreterze. Domyślne uruchomia się to w miejscu stworzonego projektu za pomocą komendy:
manage.py shell. Ta komenda odpala ipythona (ile mamy go zainstalowanego) i udostępnia w nim łatwy dostep do całego środowiska Django, jego modeli, bibliotek itp. Ipython to podrasowana wersja interpretera Pythona. Świetnie uzupełnia metody, ma dobrą historię edycji i cały szereg dodatkowych możliwości2.
Przykładem różnic w podejściu do kwestii jawności i “magii” jest inny sposób podejścia do modułów i przestrzeni nazw między Pythonem a Ruby. Python stosuje przejrzystą do bólu zasadę: moduł to po prostu plik. Wszystko co w nim jest, automatycznie jest ładowane do przestrzeni nazw określonej nazwą pliku. Python umożliwia także selektywne ładowanie z modułu wybranych klas i metod. Wszystko jest niezmiernie czytelne.
W wypadku Rubiego mamy tylko wyrażenie require które odpala i włącza plik. Przy czym z tego kodu kompletnie nic nie można wywnioskować, nawet tego, czy w środku zawiera jakikolwiek moduł. Trzeba zajrzeć aby się dowiedzieć, co zostało dorzucone do naszej przestrzeni nazw. Pythonowa zasada “jawności ponad domyślaniem się”, w tym wypadku stanowi kolejny argument na rzecz większej produktywności Pythona wobec Rubiego.
Django jest szybsze.
Django jest cholernie szybkie. Nie tylko dlatego, że korzysta z Pythona i bytecodu, ale także dlatego, że jest dobrze napisane. Np. szablony Django są szybsze od Cheetah i nie wymagają żadnej dodatkowej kompilacji! Wszystkie wyrażenia regularne używane np. do parsowania adresów URL są jednokrotnie kompilowane, są więc wykonywane bardzo szybko. Mało tego, w Django można włączyć obsługę akceleratora Psyco. Wystarczy w mojprojekt/settings.py dodać linijkę ENABLE_PSYCO = True. Railsy nie mają wydajnościowo szans z Django. Chyba tylko Pylons może się zbliżyć i nawiązać jakąś walkę na tym polu. Rails i PHP są tu bez szans.
Django pracuje na wyższym poziomie abstrakcji.
Obsługa formularzy, ich walidacja są zwykle uciążliwe i programiści mają z nimi ciągle do czynienia. Rails oraz Pylons (który to skopiował z Railsów) udostępniają cała grupę wygodnych helperów do generowania kodu html np. do formularzy. Mimo, że to jest na pewno lepsze podejście od bezpośredniego dłubania w kodzie HTML, to Django oferuje coś znacznie lepszego – kontrolki. Coś, co używa np. ASP.NET, tylko że znacznie prostsze w obsłudze.
Djangowe modele korzystające z mapowania relacyjno-obiektowego (ORM) również pracują na wyższym poziomie abstrakcji niż tylko odwzorowania prostych typów danych jakie posiada relacyjna baza danych. Uważam też, że djangowy ORM jest lepszy od SQLObject. Nie dość że ma znacznie lepszą dokumentację, to na dodatek ma więcej możliwości. Np. niby taka trywialna sprawa, ale jak w SQLObject odwzorować takie zapytanie (z takim samym traktowaniem dużych i małych polskich znaków)?
SELECT * FROM tabelka WHERE pole LIKE '%wartość%'
W Django to jest trywialna operacja i na dodatek operuje na generatorze:
for row in Tabelka.objects.filter(pole__icontains="wartość"):
print row.pole
W kolejnych częściach, zajmę się przykładem stworzenia prostej aplikacji internetowej. Pokażę też jak przeciążyć modyfikator pluralize aby uwzględniał polskie znaki, i jak obsługiwać formularze (włącznie z dynamicznym budowaniem ich treści)
-
1 Poza całym szeregiem złożonych portali, developerzy Django sprzedają Elington – profesjonalny CMS zbudowany na bazie tego frameworku. Oczywiście Railsy również powstały pierwotnie jako projekt do zastosowań komercyjnych. Stąd nic dziwnego, że oba środowiska oferują sporo interesujących rozwiązań.
2 Aby działały uzupełniania metod (po wciśnięciu klawisza Tab) trzeba doinstalować moduł readline oraz ctypes. Jeśli interpreter nie wyrzuca wyjątku po wpisaniu import readline, to znaczy że mamy go już zainstalowanego.
(Zobacz: część III)
Posted in Python, Django | Tags django | 10 comments