Ruby 1.9 (YARV) vs. Python, Lua & PHP

Opublikowane przez 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:

  1. Lua: 1.14 s
  2. Ruby 1.9: 1.49 s
  3. PHP 5.2: 1.74 s
  4. Python 2.5: 2.64 s
  5. Python 2.4.3: 2.72 s
  6. 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

  1. Ruby 1.9: 2.0953268 s.
  2. Lua 5.1: 2.4 s.
  3. Python 2.5: 2.49481592178 s.
  4. Python 2.4.3: 2.71687791348 s.
  5. PHP 5.2: 4.2 s.
  6. Ruby 1.8.5: 9.0153751 s.

Jestem ciekaw kiedy wyjdzie Rails dostosowany do Ruby 1.9. Zapowiada się bardzo obiecująco!

Posted in , ,  | Tagi , , , , ,  | 19 comments

Python 2.5 - mod_python, MySQLdb i PythonWin

Opublikowane przez Jarosław Zabiełło Tue, 26 Dec 2006 03:55:00 GMT

W końcu nie ma już chyba powodu aby nie używać nowego Pythona 2.5 pod windowsami. Od niedawna jest dostępna binarna instalka mod_pythona dla Apache 2.2. Jest też binarna instalka MySQLdb. Mimo że firma ActiveState coś zasnęła i zatrzymała się na razie na Pythonie 2.4.3, można sobie ściągnąć oddzielnie edytor PythonWin. Jest bardzo szybki i wygodny.

Posted in  | Tagi  | brak comments

Skype3 i polski czat dla Railsów, Django i Pylonsa

Opublikowane przez Jarosław Zabiełło Thu, 14 Dec 2006 23:48:00 GMT

Nowy Skype 3 wprowadza małą rewolucję w stos. do poprzedniej wersji. Można nie tylko rozmawiać, ale pograć w szachy, kółko i krzyżyk i inne gry (są b. ładnie zrobione we Flashu 9). Można nagrywać rozmowy na dysk. Można tworzyć publiczne czaty. I właśnie w tej sprawie piszę ten tekst bo stworzyłem polski czat dla miłośników frameworków Ruby on Rails, Django i Pylons. Wygodniej jest czasem skonsultować coś w czasie rzeczywistym niż na grupie czy forum dyskusyjnym.

Posted in , , , , ,  | Tagi , ,  | 17 comments

Subversion /etc/init.d script

Opublikowane przez Jarosław Zabiełło Sat, 07 Oct 2006 00:57:00 GMT

Postanowiłem troszkę poprawić napisany wcześniej skrypt startowy do Subversion. Dorzuciłem też wersję w Pythonie.

#!/usr/bin/env ruby

VERBOSE = true
USER = "nobody"
APP = "/usr/bin/svnserve"
DIR = "/home/svn-repository"
PID = "/var/run/svnserve.pid"

def script action
  def start
    unless File.exists?(PID)
      system %Q|su -c "#{APP} -d -r #{DIR}" #{USER}|
      system "echo `pidof -o %PPID #{APP}` > #{PID}"
    end
  end
  def stop
    return false unless File.exists?(PID)
    system "/bin/kill -TERM #{File.read(PID)}"
    system "/bin/rm -f #{PID}"
    true
  end
  puts "#{APP} #{action}"  if VERBOSE
  $defout.flush # unbuffered output
  case action
    when :start
      start
    when :stop
      puts "#{File.basename(APP)} cannot be stopped because it cannot find #{PID}" unless stop
    when :restart
      stop
      start
  end
end

if %w{start stop restart}.include? ARGV.first
  script ARGV.first.to_sym
else
  puts "Usage: sudo #{__FILE__} {start|stop|restart}"
end

Ten sam skrypt w Pythonie:

#!/usr/bin/env python

import os, sys

VERBOSE = True
USER = "nobody"
APP = "/usr/bin/svnserve"
DIR = "/home/svn-repository"
PID = "/var/run/svnserve.pid"

def script(action):
  def start():
    if not os.path.exists(PID):
      os.system('su -c "%s -d -r %s" %s' % (APP, DIR, USER))
      os.system('echo `pidof -o %%PPID %s` > %s' % (APP, PID))
  def stop():
    if not os.path.exists(PID): return False
    os.system('/bin/kill -TERM %s' % file(PID).read())
    return True
  if VERBOSE: print "%s %s" % (APP, action),
  if action == 'start':
    start()
  elif action == 'stop':
    if not stop():
      print "%s cannot be stopped because it cannot find %s" % (os.path.basename(APP), PID)
  elif action == 'restart':
    stop()
    start()

if len(sys.argv) == 2 and sys.argv[1] in ('start', 'stop', 'restart'):
  script(sys.argv[1])
else:
  if VERBOSE:  print "Usage: sudo %s {start|stop|restart}" % sys.argv[0]

Posted in ,  | Tagi , ,  | 2 comments

IronPython 1.o

Opublikowane przez Jarosław Zabiełło Wed, 06 Sep 2006 15:10:00 GMT

W dniu wczorajszym (5 września 2006) światło dzienne ujrzała wersja 1.0 projektu Iron Python – implementacji języka Python działającej na platformie .NET i generującej kod 100% kompatybilny z CLR (Common Language Runtime).

Iron Python może być w zasadzie uważany za produkt sygnowany przez Microsoft (pliki DLL są podpisane certyfikatem cyfrowym firmy Microsoft) i jako taki na pewno zyskuje sobie spore grono zwolenników używających go zarówno jako języka do tworzenia aplikacji jak i języka do osadzania w swoich własnych aplikacjach.

Do uruchomienia IronPython’a wymagany jest .NET Framework w wersji 2.0, gdyż wykorzystuje on jej rozszerzenia, jak na przykład typy generyczne, czy metody dynamiczne. Deweloperzy otrzymują do ręki, podobnie jak w standardowym Pythonie, interaktywną konsolę z interpreterem języka, ale także możliwość statycznej kompilacji do plików wykonywalnych “exe”, czy bibliotek “dll”. Pomimo zgodności samego języka, obie implementacje jednak się różnią, jak choćby nieobecnością wielu standardowych modułów. Szczegółowe informacje o różnicach można znaleźć na witrynie projektu.

Zobacz artykuł “Microsoft i dynamiczne języki”.

Zobacz stronę “Jim Hugunin’s Thinking Dynamic ” zawierającą informację autora IronPythona o wydaniu wersji 1.0.

Zobacz prezentację wideo: The Screening Room. #8 August 2006: IronPython (dodane: 10 września 2006)

Posted in  | Tagi , ,  | 1 comment

MySQLdb & client encoding

Opublikowane przez 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 , ,  | Tagi , , ,  | 4 comments

PyQT 4

Opublikowane przez Jarosław Zabiełło Tue, 13 Jun 2006 02:21:00 GMT

Firma Riverbank Computing ogłosiła wypuszczenie PyQT4, pythonowego wrappera do biblioteki QT4 firmy Trolltech. Wygląda to imponująco: 8 głównych modułów, 400 klas, 6 tys. metod i funkcji. Szczególnie dobre wrażenie robi Designer (do budowania GUI) Assistamnt z naprawdę ładnie przygotowana dokumentacją. QT zawsze uchodziła za bibliotekę szybką, ładną i dobrze napisaną. Jedyny wcześniejszy problem wersją 3 dotyczył konieczności opłat za użytkowanie pod Windowsami. Jednak wersja 4.x jest już w pełni open source! Czyżby rozpoczął się na dobre proces wypierania wxPythona i PyGTK?

Posted in  | Tagi ,  | 3 comments

Django - zabójcza aplikacja. Część III.

Opublikowane przez 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.py2. Na początku pliku ustawiamy:
import sys

if sys.platform == 'win32':
    DEBUG = True
else:
    DEBUG = False
TEMPLATE_DEBUG = DEBUG

ADMINS = (
    ('Administrator', '[email protected]'),
    )

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 = '' # domyslnie bedzie localhost
else:
    # pod linuksem zwykle korzystamy z szybkiego gniazda unix
    DATABASE_HOST = '/var/run/mysqld/mysqld.sock'
DATABASE_PORT = '' # czyli domyślnie będzie 3306

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' # domyślnie był angielski
#... 
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:
manage.py shell
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:

manage.py runserver

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

#-*- coding: utf-8 -*-

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')),
    #(r'^admin/', include('django.contrib.admin.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'),
   #(r'^admin/', include('django.contrib.admin.urls')),
)

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:

#-*- coding: utf-8 -*-

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()
    # tworzę kopię parametrów przekazanych z formularza
    _GET = request.GET.copy()
    if request.GET:
        # sprawdzam czy formularz nie ma błędów
        errors = manipulator.get_validation_errors(_GET)
        if not errors:
            manipulator.do_html2python(_GET)
           # wyszukuję cytaty 
           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 }} 
            &quot;{{ row.verse }}&quot;
        </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' # utf8
    except ValueError: # invalid string that's not a number
        pass
    except TypeError:
        try:
            if int(value) in (2,3,4):
                return 'y'
            elif int(value) >= 5:
                return '\xc3\xb3w' # utf8            
        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.

{% load plugins %}

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>
        &copy; 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 }} 
            &quot;{{ row.verse }}&quot;
        </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 ,  | Tagi  | 8 comments

Python 2.4 Quick Reference Card

Opublikowane przez Jarosław Zabiełło Wed, 31 May 2006 08:53:00 GMT

Wyszła świetna, 18 stronicowa, ściąga do Pythona 2.4. Plik jest w formacie PDF i można pobrać stąd.

Posted in  | Tagi  | brak comments

Django - zabójcza aplikacja. Część II.

Opublikowane przez 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 ,  | Tagi  | 10 comments

Django - zabójcza aplikacja.

Opublikowane przez Jarosław Zabiełło Sat, 27 May 2006 08:17:00 GMT

Muszę przyznać, że podoba mi się zarówno sposób, jak i kierunek, w którym rozwijany jest pythonowy framework Django. Nie jest to kolejny domowy projekt tworzony w ramach hobby (jak to było z CherryPy). Django powstawało od razu z założeniem pracy w ciężkim środowisku produkcyjnym. Ostatnio napisałem sobie (w ramach zabawy) pewną aplikację w Django i muszę przyznać, że praca z Django jest faktycznie komfortowa. Wyższa niż w Pylons, i wyższa nawet niż w Railsach. Aby tekst nie był za długi, podzielę go na kilka mniejszych tekstów. Przedstawię swoje spostrzeżenia co do Pythona jako języka na tle Rubiego. Potem opiszę powody dla których Django ma wielkie szanse stać się killer application dla Pythona. Oraz w kolejnych częściach opiszę krok po kroku tworzenie konkretnej aplikacji w Django. Dziś będzie krótko o Pythonie.

Przewaga Pythona

Gdy zaczynam mówić o Django, to od razu nasuwa mi się porównanie do Ruby on Rails. Django jest oparty na języku Python, a Rails na języku Ruby. Co by nie mówić o zaletach Rubiego, to Python posiada kilka cech, które moim zdaniem, stawiają go wyżej1:

Python jest szybszy.

To po prostu jest fakt i to widać gołym okiem. Python automatycznie kompiluje każdy moduł do bytecodu. To jest operacja całkowicie przezroczysta dla programisty. Bytecode przy dużym projekcie składającym się w setek plików przyśpiesza znacznie czas ładowania kodu i może od biedy służyć również jako proste zabezpieczenie źródeł. Python jest szybszy nie tylko od Rubiego, ale także od PHP. Robiłem kiedyś porównanie prostej aplikacji w mod_php5 vs Myghty. Zdziwiło mnie wtedy to, że Python był tu szybszy, bo PHP jest zoptymalizowany do szybkiego wykonywania prostych skryptów. Oczywiście, im większy kod, tym rośnie przewaga Pythona z powodu użycia bytecodu2.

Python jest łatwiejszy do nauki.

Pythona łatwiej można się nauczyć i posługiwać niż Rubiego czy nawet PHP (sic!). To jest bardzo ważny punkt, bo dotyczy kluczowej kwestii jaką jest produktywność programisty.

Filozofią Pythona jest aby było jak najmniej możliwych dróg do rozwiązania tego samego problemu. Z tego wynika, że Python stara się implementować jak najmniejszą ilość funkcji i metod. Znacznie to ułatwia poruszanie się po tych metodach. W wypadku Rubiego, nie dość, że mamy znacznie więcej metod na obiekt, to na dodatek, lista metod jest dodatkowo zaśmiecona aliasami. W Ruby mamy po kilka metod, które wykonują to samo. Po kilka konstrukcji językowych, które robią to samo. Widać tu korzenie fascynacji Perlem. Ale dla programistów to jest uciążliwość.

Python jest bardziej produktywny.

Dodatkowym czynnikiem ułatwiającym szybką prace, jest integracja prostego mechanizmu dokumentacji kodu na poziomie składni Pythona. Mowa o docstringach. W praktyce znaczy to, że prawie wszystkie edytory IDE (których jest sporo, są darmowe i są bardzo dobre jak na tę klasę języków) nie tylko rozwijają mniej metod, ale także wyświetlają ich zintegrowaną dokumentację. Efektem jest znacznie mniej czasu traconego na zaglądanie do dokumentacji czy książek. To duża, odczuwalna różnica. Mniej rzeczy do zapamiętania, podpowiedzi do składni na każdym kroku, to musi się odbijac na produktywności tworzenia kodu3.

Python ma pełną obsługę Unicode.

Pełnej obsługi Unicode nie ma ani PHP ani Ruby. Obsługują co najwyżej UTF-8 i to w ograniczonym zakresie. Np. próby konwersji znaków ze standardu cp1250 (typowa sytuacja dla polskiego serwera MSSQL) do latin2 (typowe dla serwera MySQL w internecie) skazane są na korzystanie z biblioteki iconv, co jest dosyć chorym rozwiązaniem. Chorym, bo iconv nie umie dawać sobie rady z wieloma znakami, które nie mają odpowiednika w standarcie latin2. Chodzi nie o polskie ogonki, ale o różnego rodzaju długie myślniki, polskie, dolne cudzysłowy itp.

>>> s = " „To jest jakiś tekst — spróbuj to skonwertować z cp1250 do latin2.”  "
' \x84To jest jaki\x9c tekst \x97 spr\xf3buj to skonwertowa\xe6 z cp1250 do latin2.\x94  '
>>> s = " „To jest jakiś tekst — spróbuj to skonwertować z cp1250 do latin2.”  "
>>> s
' \x84To jest jaki\x9c tekst \x97 spr\xf3buj to skonwertowa\xe6 z cp1250 do latin2.\x94  '
>>> u = unicode(s, 'cp1250')
>>> u
u' \u201eTo jest jaki\u015b tekst \u2014 spr\xf3buj to skonwertowa\u0107 z cp1250 do latin2.\u201d  '
>>> print uTo jest jakiś tekstspróbuj to skonwertować z cp1250 do latin2.”  
>>> latin2 = u.encode('iso-8859-2', 'xmlcharrefreplace')
>>> latin2
' &#8222;To jest jaki\xb6 tekst &#8212; spr\xf3buj to skonwertowa\xe6 z cp1250 do latin2.&#8221;  '

Nie chodzi tylko o konwersje znaków. Dzięki Unicode, w Pythonie możesz dokonywać rzeczy bardzo trudnych do uzyskania w PHP czy Ruby. Operacje na tekście potrafią poprawnie traktować polskie znaczki. Np. napisanie funkcji implementującej podświetlanie wyszukiwanych słów bez względy na to czy uzyto dużych, czy małych polskich znaków. Dla bazy SQL to żaden problem4 ale dla kodu?

Chcielibyśmy aby wyrażeniem regularnym podmienić jakieś słowa zawierające polskie znaczkiNp. wyrażenia regularne umieją reagować poprawnie na polskie ogonki!

#-*- coding: utf-8 -*-
import re
def highlight(tekst, slowo, encoding):
    if not isinstance(tekst, unicode):
        tekst = unicode(tekst, encoding)
    if not isinstance(slowo, unicode):
        slowo = unicode(slowo, encoding)
    replace = re.compile('('+re.escape(slowo)+')', re.I|re.U).sub
    return replace(r'<b>\1</b>', tekst)
print
print highlight("podświetl gęś GĘŚ, GęŚ", "gęś", 'cp1250') 
# => podświetl <b>gęś</b> <b>GĘŚ</b>, <b>GęŚ</b>

Jeszcze jedno. Microsoft “uszczęśliwił” nasz kilkoma standardami kodowania polskich znaków. Inny jest dla windows (cp1250) a inny dla konsoli okienka DOS (cp852). Python pozwala całkowicie zapomnieć o stringach i kodowaniach. Po prostu należy pracować na obiektach Unicode. Powtarzam: obiektach Unicode a nie stringach UTF-8, bo to nie to samo. Instrukcja print wyświetli poprawnie polskie znaki, jeśli operujemy na obiekcie unicode.

Python ma stabilniejsze i dojrzalsze biblioteki.

Przekonałem się na swojej skórze, co znaczy przekonwertować setki plików graficznych za pomocą biblioteki RMagick (Ruby), a za pomocą PIL (Python). Kod w Ruby pożarł mi cała pamięć i odmówił pracy…

-

1 Osobną kategorią jest język PHP. Właściwie szkoda gadać. Ci, co zakosztowali pracy z Pythonem i Ruby, tracą szybko zapał do mozolnego dłubania w chaotycznym, niespójnym, źle zaprojektowanym języku PHP. Na ten temat to chyba trzeba napisać oddzielny tekst. Jak znajdę czas, to sobie “poużywam” na PHP. :)

2 Aby PHP aż tak nie odstawał, trzeba użyć akcelerator. Niektóre złożone projekty PHP, jak ezPublish, bez akceleratora są koszmarnie wolne.

3 Najlepszym przykładem różnicy między Pythonem a np. .NET jest ostatnio zaimplementowany przez mnie system synchronizacji danych między serwerami. Projekt tworzony przez 6 miesięcy w VB.NET (.NET) został napisany od zera w 5 roboczych dni (z czego działający prototyp był dostępny po paru godzinach). Żeby było ciekawiej, nowy kod Pythona w tym wypadku działa i szybciej i stabilniej.

4 Np. dla MySQL (od wersji 4.1 wzwyż) trzeba tylko ustawić aby baza pracowała natywnie w utf8 (domyślnie jest latin1) i tabele oraz ich pola tekstowe miały ustawiony parametr collations na utf8_polish_ci.

(Zobacz: część II)

Posted in ,  | Tagi  | 10 comments

Programować jak kaczka

Opublikowane przez Jarosław Zabiełło Fri, 12 May 2006 16:12:00 GMT

“Jeśli coś chodzi jak kaczka, wygląda jak kaczka i kwacze jak kaczka, to z pewnością jest to… kaczka”

Powyższe zdanie jest żartobliwym ujęciem istoty pewnej praktyki programistycznej (popularnej szczególnie w wysokopoziomowych językach dynamicznych takich jak Python, Ruby czy Smalltalk). Najlepiej wyjaśnić to na przykładzie (zresztą wziętym z życia).

Tak się złożyło, że ktoś napisał pewną aplikację w Delphi która wypełniała jedno z pól bazy MSSQL w formacie… RTF. I wszystko było w porządku, do czasu, aż zapadła decyzja, aby te dane wykorzystywać na stronie internetowej. Przeglądarki nie potrafią wyświetlić poprawnie formatu RTF, te dane trzeba jakoś przekonwertować do HTML.

Oczywiście, do tego zadania wybrałem Pythona. Głównie dlatego, że cały system synchronizacji serwerów już wcześniej napisałem w Pythonie. Więc naturalnym było, że dla dodania dodatkowej opcji użyję tego samego języka.

Trzeba było trochę poszukać gotowego kodu do parsowania formatu RTF, bo komu by się chciało samemu pisać parser od podstaw? :) Na szczęście biblioteka się znalazła . Jak to najczęściej bywa z drobnymi projektami open-source: dokumentacji: niet. Ale na szczęście Python jest bardzo przejrzysty i posiada cudowną cechę dla leniwych programistów: docstringi. Chwila oglądania kodu i już widzę, że w środku jest jedna z klas o obiecującej nazwie: Rtf2Html. Patrzę na wywołanie:

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print 'Usage : %s fichier.rtf' % sys.argv[0]
    else:
        finput = open(sys.argv[1],'r')
        foutput = open(finput.name + '.html','w')
        foutput.write('<html><body>')
        tester = Rtf2Html(foutput)
        # reszta kodu...

Widać, że jest tu jeden problem. Ja potrzebuję funkcji operującej na łańcuchach znaków, a tu kod operuje na … obiekcie plikowym. Nie chce mi się zmodyfikować tego kodu. Z pomocą przychodzi duck typing. Zamiast obiektu plikowego podsuniemy kontruktorowi klasy coś co będzie udawać plik. Dla pliku tak naprawdę najważniejsze są dwie metody: read() i write(). Stworzymy więc klasę, która będzie miała te dwie metody i… nic więcej. Skoro będzie kwakać jak kaczka i zachowywać się jak kaczka, to dla tego kodu niczym się nie będzie różnić od pełnej, pierzastej kaczki.

from rtf.Rtf2Html import Rtf2Html

class PseudoPlik(object):
    """Kwa, kwa, kwa ;)"""
    __content = ''
    def write(self, txt):
        self.__content += txt
    def read(self):
        return self.__content

def rtf2html(rtf):
    """RTF to HTML converter"""
    fake = PseudoPlik()
    r = Rtf2Html(fake)
    r.feed(rtf)
    return r.read()

Jak widać, nie trzeba było w ogóle modyfikować niczego w kodzie biblioteki. Wystarczyło tylko podsunąć jej coś, co wygląda jak plik. Reszta tu nie miała znaczenia, bo i tak wykorzystywane były tylko te dwie metody obiektu plikowego.

Posted in  | Tagi  | 6 comments

Lightpd & FastCGI vs. Apache & mod_php,mod_python...

Opublikowane przez Jarosław Zabiełło Mon, 24 Apr 2006 17:40:00 GMT

Serwer Apache jest bardzo popularnym i solidnym serwerem www. Nie dziwi więc powszechne używanie jego modułow do obsługi Perla, PHP czy Pythona. Uzywanie mod_php jest wręcz nagminne (cały serwis sourceforge.net stoi na mod_php). Podobnie twórcy pythonowego frameworku Django zalecają używanie mod_pythona. W dokumentacji piszą, że:

“jeśli chcesz używać Django na serwerze produkcyjnym, uzywaj Apache’a z mod_pythonem (...) Django wymaga Apache 2.x oraz mod_python 3.x.”

Zupełnie inne podejście zalecają twórcy frameworka Ruby on Rails. Nie dość, że zalecają użycie modułu FastCGI to na dodatek, zalecają (o grozo:) używanie innego serwera www: Ligttpd. Które podejście jest lepsze?

Problem z uniwersalnością

Korzystanie z modułów Apache’a wiąże nas na dobre i złe z tym serwerem. korzystanie z fastcgi daje nam większe pole manewru. Możemy korzystać z Apache, Lighttpd, IIS i innych serwerów www.

Problem stabilności serwera

Jakikolwiek błąd w skryptach pracujących w trybie mod_php czy mod_python odbija się bardzo niebezpiecznie na stabilności całego serwera. Jeden błędnie działający skrypt może zawiesić cały serwer! Dotyczy to nie tylko Apache, ale także windowsowego IIS i modułów ISAPI. Co ciekawe, moduł ISAPI dla języka PHP jest wiecznie w fazie niestabilnej i testowej. Co z tego, że jest szybszy niż klasyczny CGI, skoro jeden drobny błąd w skrypcie PHP potrafi zawiesić cały serwer i unieruchomić cała sieć intranetową na nim opartą? To jeden z powodów dla których PHP nie nadaje się najlepiej do pracy pod Windowsami (wiem, że istnieje Apache/win32 ale z różnych powodów, których tu nie będę rozwijał, korporacyjne sieci intranetowe oparte na Windows Server wolą używać IIS)

Procesy FastCGI są izolowane od serwera. Jak któryś się zawiesi, to serwerowi nic się nie stanie. Proces można ubić, zrestartowac. Daje to znacznie większą stabilność pracy serwera.

Problem bezpieczeństwa

Każdy skrypt odpalany w trybie mod_php na Apache’u jest odpalany w kontekście tego samego uzytkownika. Tworzy to dosyć poważną dziurę bezpieczeństwa na serwerach hostingowych. Każdy użytkownik na serwerze za pomocą prostego kodu PHP może przeglądać źródła dowolnego pliku innych użytkowników. Wszyscy mają dostęp do wszystkich! Napisałem nawet swego czasu prosty skrypt wedrujący po całym sourceforge.net i przeglądający źródła wszystkich dostępnych tam projektów. :) Każdy, średnio zaawansowany programista z łatwością napisze sobie taki kod. Nie ma znaczenia, czy to będzie Perl (mod_perl), Python (mod_python) czy PHP (mod_php).

W FastCGI tego problemu nie ma. Każdy użytkownik może pracować na swoich prawach w pełnej izolacji. Wystarczy każdemu przydzielić po procesie FastCGI na prawach danego użytkownika.

Marnowanie możliwości Apache’a 2.x

To może nie jest najważniejszy problem. Apache 2.x potrafi pracować w trybie wielowątkowym (worker) lub wieloprocesowym (prefork) który jest zgodny ze starym Apache 1.x . Ten pierwszy jest nowocześniejszy, szybszy i pożera mniej pamięci. Problem jednak w tym, że najbardziej popularny język skryptowy nie posiada poprawnie zaimplementowanej wielowątkowości. Jeśli więc chcesz używać PHP, to zapomnij o trybie workera. Musisz pracować w starym trybie prefork. Zajmuje to więcej pamięci, ale za to nie będzie problemów z różnymi bibliotekami PHP.

Problem zasobożerności

Gdy uruchamiamy Apache 1.x lub Apache 2.x w trybie prefork, uruchamiamy szereg podprocesów. Każdy z nich zajmuje określoną ilość pamięci. Każdy z zainstalowanych modułów… również. A to przecież nie ma sensu! W praktyce nie chcemy wykorzystywać wszystkich procesów do przetwarzania mod_pythona. Większość procesów jest zajęta podawaniem statycznej wartości (obrazki, pliki ze stylami kaskadowymi, skryptami JavaScript itp).

FastCGI pozwala na ograniczenie ustalenie ilości swoich procesów w sposób niezależny od wszystkich procesów serwera www. Np. możemy sobie zadecydować, aby 100 procesów zajmowało się wartością statyczną, a tylko 10 wartością dynamiczną. Takie coś jest niemożliwe wypadku korzystania z mod_php czy mod_pythona. A oszczędności na zużyciu pamięci będą kolosalne!

Np. można załozyć, że pojedyńczy proces Apache podczas serwowania statycznych stron zajmie nie więcej niż ok 5 MB pamięci. Natomiast w wypadku użycia mod_ruby może wzrosnąć nawet do ok. 20-30MB na proces. Użycie zatem 100 procesów Apache’a z tylko 10 procesami FastCGI zużyje ok. 800MB pamięci. Zaś użycie mod_ruby bez problemu pożre nawet i 3GB!

Dlaczego Lighttpd a nie Apache?

Na końcu chciałbym jeszcze zastanowić się czy twórcy Railsów nie mają racji i czy w ogóle nie warto zrezygnować z Apache na rzecz Lighttpd? Są generalnie 3 powody za i jeden przeciw.

Za: lepiej dopracowany moduł FastCGI

Moduł FastCGI do Apache’a ma opinię nie do końca stabilnego i dopracowanego. To powoduje obawę większości użytkowników od używania go dla serwera Apache. Z kolei serwer Lighttpd od samego początku kładł nacisk na dobrą implementację swego modułu FastCGI. Jest on stabilniejszy i lepiej dopracowany niż pod Apachem.

Za: Wbudowana możliwość rozkładania obciążenia

Lighttpd ma wbudowany mechanizm rozkładania ruchu na wiele serwerów. Jeśli więc nawet ktoś upiera się przy Apache’u, to w wypadku kiedy jeden serwer nie jest wystarczający do obsługi ruchu, może warto rozważyć aby postawić na froncie jeden serwer Lighttpd który by rozkładał ruch pomiędzy kilka serwerów?

(Najnowszy Apache 2.2 ma posiadać podobny mechanizm. Jednak jest to jeszcze bardzo świeża wersja, która (w momencie pisania tego tekstu) nie ma nawet prekompilowanej instalacji dostępnej dla Windowsów oraz nie wiadomo jak jest ze stabilnością wszystkich modułów.)

Za: Lighttpd jest szybszy

O ile najnowszy Apache 2.2 nie zmieni sytuacji, to wszystko wskazuje na to, ze Lighttpd jest po prostu szybszy. Nie tylko zdecydowanie szybciej podaje statyczne dane ale także szybciej działa PHP pod Lighttpd niż mod_php pod Apachem. To nie są drobne różnice. To są różnice rzędu 2-3 razy!

Przeciw: Apache ma więcej modułów

Właściwie jedynym powodem aby używać Apache zamiast Lighttpd jest to, że do Apache napisano znacznie więcej różnych modułów. Ale szczerze mówiąc, w praktyce to rzadko ma znaczenie. Większość osób uzywa głównie kilku modułów. Zaś Lighttpd posiada praktycznie wszystko, co potrzeba: mod_fastcgi, mod_cgi, mod_redirect, mod_access, mod_auth, mod_compress, mod_webdav, mod_ssl, mod_alias, mod_proxy, mod_rewrite, itp. itd.

Lighttpd + FastCGI. To działa!

Na szczęście, dzięki standardowi WSGI, użytkownicy Pythona nie muszą słuchać zaleceń developerów Django. Teraz praktycznie wszystkie frameworki Pythona pozwalają dzięki WSGI korzystać z dobrodziejstw FastCGI. Ja również na swoim serwerze wyrzuciłem Apache’a i zastapiłem go Lighttpd. Podaje zarówno strony statyczne jak i obsługuje ten blog, a nawet PHP 5.1. Nawet Plone mi udało mi się uruchomić z Lighttpd na przodzie.

Posted in , ,  | Tagi , , ,  | 11 comments

Ruby vs. Python - operacje na plikach

Opublikowane przez Jarosław Zabiełło Tue, 11 Apr 2006 19:47:00 GMT

Ostatnio miałem potrzebę napisania skryptu kopiującego całe pliki z całego drzewa folderów. Problem w tym, że chciałem kopiować tylko pliki, które nie miały ustawionych atrybutów tylko do odczytu. I to musiało być zrobione pod Windowsami. Zwykle jestem przyzwyczajony że tego typu zadania to piszę w Pythonie w kilka minut. Nie spodziewałem się że tu będzie inaczej….

Okazało się że Python (2.4.3) nie ma porządnie zaimplementowanych metod sprawdzania atrybutów pliku. Na próżno szukałem w manualu, Python Cookbook , internecie.

Zgodnie z prawami Murphego rozwiązanie znalazłem jak już było niepotrzebne (w międzyczasie zdążyłem napisać to w Ruby).

>>> os.stat(r'c:\tmp\test1')[stat.ST_MODE] & stat.S_IWRITE
128
>>> os.stat(r'c:\tmp\test2')[stat.ST_MODE] & stat.S_IWRITE
0

Wygląda to raczej paskudnie na tle analogicznego kodu Rubiego:

>>> File.writable?('c:\tmp\test1')
true
>>> File.writable?('c:\tmp\test2')
false

Może to szczególny przypadek, ale jakoś nie sprawdziła się w tym wypadku prostota Pythona. Metody Rubiego w tym temacie są znacznie bardziej intuicyjne. Zresztą zobaczmy poniższy przykład z manuala Pythona:

import os, sys
from stat import *
def walktree(top, callback):
    for f in os.listdir(top):
        pathname = os.path.join(top, f)
        mode = os.stat(pathname)[ST_MODE]
        if S_ISDIR(mode):
            walktree(pathname, callback)
        elif S_ISREG(mode):
            callback(pathname)
        else:
            print 'Skipping %s' % pathname
def visitfile(file):
    print 'visiting', file
if __name__ == '__main__':
    walktree(sys.argv[1], visitfile)

Ten sam kod w języku Ruby wygląda tak:

require 'find'
if $0 == __FILE__
  Find.find(ARGV[0]) do |f|
    if File.file?(f)
      puts 'visiting ' + f 
    elsif not File.directory?(f)
      puts 'skipping ' + f
    end
  end
end

I kto tu powie, że Ruby jest mniej czytelny od Pythona?

Posted in ,  | Tagi ,  | 8 comments

Microsoft docenia Pythona

Opublikowane przez Jarosław Zabiełło Tue, 21 Mar 2006 09:46:00 GMT

Na stronie MicrosoftTechNet firma Microsoft umieściła sporą listę przydatnych skryptów Pythona dotyczących różnych aspektów systemu Windows.

Posted in  | Tagi  | 3 comments

Subway - padł

Opublikowane przez Jarosław Zabiełło Sat, 18 Mar 2006 13:05:00 GMT

Subway, jeden z pierwszych z frameworków napisanych w Pythonie (który chciał konkurować z RoR, za pomoca podobnego stylu pracy) został zamknięty z powodu braku zainteresowania i wsparcia ze strony społeczności pythonistas.

Osobiście, mnie to nie dziwi. Subway nigdy nie wysilił się aby udostępnić jakąś sensowną dokumentację. Trudno aby ktokolwiek chciał wspierać coś, co nie wiadomo jak używać.

Tak więc, na polu frameworków Pythona które są w jakiś sposób podobne do RoR zostały: Pylons, Django i TurboGears.

Posted in  | Tagi ,  | 3 comments

Mechanizmy bezpieczeństwa - Ruby vs Python

Opublikowane przez Jarosław Zabiełło Fri, 10 Mar 2006 21:37:00 GMT

W języku Ruby (mimo jego wielkiego dynamizmu) kwestia bezpieczeństwa została potraktowana poważnie. Ruby posiada różne mechanizmy umożliwiające kontrolę nad bezpieczeństwem kodu. Generalnie są to 3 różne mechanizmy.

  • Zakresy metod: private, protected i public.
  • Możliwość nieodwracalnego zamrożenia dowolnego obiektu.
  • Możliwość nieodwracalnego ustawiania poziomów bezpieczeństwa.

Zakresy private, protected, public

  • Metody publiczne moga być wywoływane przez kogokolwiek. Domyślnie wszystkie metody są publiczne (poza konstruktorem initialize który zawsze jest prywatny)
  • Metody chronione (protected) mogą być wywoływane tylko przez obiekty w tej samej klasie i klasach potomnych.
  • Metody prywatne mogą być wywoływane tylko w kontekście bieżącego obiektu. Nie można wywoływać metod prywatnych innego obiektu.

Np.:

class C
  public
  def publiczna
    p "Metoda publiczna"
  end  
  def get_chroniona
    chroniona
  end
  def get_prywatna
    prywatna
  end
  protected
  def chroniona
   p "Metoda chroniona"
  end 
  private
  def prywatna
    p "Metoda prywatna"
  end 
end

c = C.new
c.publiczna # => Metoda publiczna
c.get_chroniona  # => Metoda chroniona
c.get_prywatna  # => Metoda prywatna
c.chroniona # => protected method `chroniona' called for #<C:0x28e6300> (NoMethodError)
c.prywatna # => private method `prywatna' called for #<C:0x28e62e8> (NoMethodError)

Dla porównania, w Pythonie wszystkie metody są publiczne. Efekt metod prywatnych i chronionych uzyskuje się za pomocą konwencji. Metody prywatne mają prefiks dwóch underscorów, a chronionie – jeden underscore. Oczywiście nadal są one formalnie publicznie ale dostęp do nich (szczególnie do metod “prywatnych” jest utrudniony)

class C(object):
  def publiczna(self):
    print "Metoda publiczna"
  def get_chroniona(self):
    self._chroniona()
  def get_prywatna(self):
    self.__prywatna()
  def _chroniona(self):
   print "Metoda chroniona"
  def __prywatna(self):
    print "Metoda prywatna"

c = C()
c.publiczna()
c.get_chroniona() # => Metoda chroniona
c.get_prywatna() # => Metoda prywatna
c._chroniona() # => Metoda chroniona
c._C__prywatna() # => Metoda prywatna
c.__prywatna() # => AttributeError: 'C' object has no attribute '__prywatna'

Python wychodzi z innych przesłanek niż Ruby. Zakłada, że “jesteśmy dorośli i wiemy co chcemy robić”. Tzn. utrudnia popełnienie pomyłki przypadkowego dostępu do metody “prywatnej” za pomocą zmodyfikowania nazwy która da dostęp do takiej metody. Jednak jak ktoś, chce to Python nie będzie utrudniał. Można dostać się do każdej metody. Jak zobaczymy dalej (przy omawianiu poziomów bezpieczeństwa Rubiego), ta zasada spowodowała daleko idące konsekwencje dla kwestii bezpieczeństwa kodu Pythona.

Zamrażanie obiektów

Jeśli chodzi o kwestie zamrażania obiektów, to opisuję to w artykule o dynamice klas Python nie posiada jednak aż tak eleganckiego mechanizmu co Ruby. Da się co prawda przyblokować zmiany w obiekcie, ale trzeba sięgnąć po bardziej zaawansowane mechanizmy:

class ImmutableClass(type):
  def __setattr__(cls, name, value):
    raise AttributeError, "'%s' is immutable" % cls.__name__

class C(object):
  def hello(self):
    return 'Hello World!'
  __metaclass__ = ImmutableClass # blokada zmiany

def hello2(self): return 'Blah!'

c = C()
print c.hello() # => Hello World!
C.hello = hello2 #=> AttributeError: 'C' is immutable
print c.hello() # => Hello World!

Poziomy bezpieczeństwa

Jedną z ciekawych cech języka Ruby jest możliwość uruchamiania kodu na różnym poziomie bezpieczeństwa. Załóżmy np, że chcemy stworzyć (w najprostszy możliwy sposób) kalkulator który następnie udostępnimy na stronie internetowej. Przykładowy kod (na podst. PixAxe2) mógłby wyglądać np. tak:
require 'cgi'
cgi = CGI.new('html4')
# pobierz warto z pola formularza 'wyrazenie'
expr = cgi['wyrazenie'].to_s
begin
  result = eval(expr)
rescue Exception => detail
  # obsłuż złe wyrażenie
end
# wyświetl wynik dla użytkownika
Wszystko pięknie, ale co się stanie jak jakiś złośliwy użytkownik wpisze:
system("rm *")

Cały nasz kod zostanie skasowany! Na szczęście Ruby udostępnia mechanizm uruchamiania kodu w specjalnym trybie tak, aby kod nie był w stanie zbytnio rozrabiać. Poziom Rubinowej paranoi bezpieczeństwa określa zmienna globalna $SAFE. Może ona przyjmować następujące wartości:

  • 0 Brak mechanizmów zabezpieczeń. Wartość domyślna.
  • >= 1 Ruby zabrania uzywanie danych w potencjalnie niebezpiecznych operacjach
  • >= 2 Ruby zabrania ładowanie innych skryptów z dostępnych globalnie miejsc
  • >= 3 Wszystkie nowo stworzone obiekty są oznakowane jako niebezpieczne
  • >= 4 Ruby dzieli uruchamiany program tak, że obiekty nie oznaczone jako niebezpieczne również nie mogą być modyfikowane.

Domyślna, zerowa wartość $SAFE jest wystarczająca w większości przypadków. Jednak, gdy skrypt Rubiego jest uruchamiany z ustawionym atrybutem suid lub setgid (co pod uniksem umożliwia uruchomienie skryptu w kontekście innego użytkownika) lub, gdy ruby pracuje w trybie modułu apache’a (mod_ruby), to poziom bezpieczeństwa jest automatycznie podnoszony do 1. Poziom bezpieczeństwa można także ustawić za pomocą parametru -T z wiersza poleceń lub poprzez przypisanie zmiennej $SAFE innej wartości. Przy czym, nie jest możliwe zmniejszenie raz ustawionego poziomu bezpieczeństwa aż do końca działania skryptu.

Bieżąca wartość $SAFE jest dziedziczona przez wszystkie nowo tworzone wątki. Jednakże wewnątrz każdego wątka, wartość zmiennej globalnej $SAFE może być zmieniana bez wpływu na tą wartość w innym wątku. Ta cecha pozwala na budowanie bezpiecznych piaskownic (sandbox), obszarów w których można odpalać potencjalnie niebezpieczny kod bez ryzyka, że wpłynie on na resztę aplikacji czy systemu. Np.
File.new(filename, 'w').write('niebezpieczny kod')
Thread.start do
  $SAFE = 4
  load(filename, true)
end

Dla porównania, Python w ogóle nie posiada takich mechanizmów. Na domiar złego, okazało się, że (obecny we wcześniejszych wersjach Pythona) moduł Bastion okazał się dziurawy Został usunięty w biblioteki podstawowej i jak na razie Python nie posiada już nic w tym temacie do zaoferowania w zamian.

Posted in ,  | Tagi ,  | 3 comments

Dokumentacja dla leniwych - potęga Pythona

Opublikowane przez Jarosław Zabiełło Mon, 06 Mar 2006 15:18:00 GMT

Jedną z najbardziej nielubianych rzeczy przez programistów jest tworzenie dokumentacji do kodu. Wszyscy wiemy, że dobrze ją tworzyć, ale mało komu chce się na to poświęcać cenny czas zwłaszcza jak terminy ukończenia projektu gonią. W takich sytuacjach często dokumentację zostawia się zwykle “na później” jako zadanie do wykonania po ukończeniu projektu. Jednak czym innym jest dodanie komentarza do kodu na bierząco a czym innym napisanie go po paru tygodniach (zwłaszcza jak kod był tworzony zespołowo i musimy teraz zastanowić się co kolega Jasio miał na myśli tworząca jakąś tam kolejną funkcję)

Aby ten problem rozwiązać, praktycznie wszyscy developerzy różnych języków zaczęli wymyślać różne metody aby ten proces usprawnić.

Po pierwsze, stworzono parsery kodu źródłowego, który potrafią wyłuskać z wszystkich plików projektu definicje klas, metod, modułów. Taka pomoc, choć lepsza nic nic, generalnie niewiele jest warta, bo sprowadza się do wyświetlenia nazw klas i ich metod (No może dodatkowo z typami danych, jeśli językiem tym jest któryś z języków statycznie typowanych) Nie mówi jednak nic na temat funkcjonalności ani tego jak używać wyłuskany kod.

Aby temu zaradzić wymyślono inne podejście. Założono że programista przed każdą funkcją czy klasą będzie dodawał wielowierszowy komentarz wg ściśle określonej składni. I potworzono różnego rodzaju biblioteki javadoc, phpdoc czy rubydoc. Niby lepiej. Można teraz lepiej opisywać kod, ale nadal trzeba trzymać się specyficznej składni i znaczników. Poza tym, tak tworzona dokumentacja nie jest w żaden sposób związana z tworzonym kodem.

Doctringi

Jednym z założeń leżących u podstaw Pythona jest przyjęcie zasady, że proste jest lepsze niż złożone. Twórca Pythona, wychodząc naprzeciw zapotrzebowaniom programistów wpadł na genialnie prosty i wygodny zarazem sposób. Przyjął prostą zasadę: każdy fragment tekstu zaczynający się zaraz pod deklaracji klasy czy metody będzie automatycznie dołączany do tej klasy, metody jako jego osobista dokumentacja. Nazwano to docstringiem.

Jak wiemy, dobry programista do programista leniwy. Czyli taki, który stara się osiągać cel jak najmniejszym nakładem pracy. Docstring nie narzuca na programistę żadnych wymagań co do konieczności używania jakichś specjalnch znaczników. Po prostu, deklaruję funkcję. Zaraz pod nią wpisuję linijkę opisującą z grubsza do czego służy, i nie przerywając pracy, piszę dalej. W związku jednak z tym, że ten docstring staje się automatycznie częścią danej metody, mam natychmiastowy do niego dostęp z poziomu interpretera, ba, z poziomu konsoli:

Każdy docstring mogę także wyświetlić tak:

W Pythonie (od wersji 2.4 wzwyż) można dodawać docstringi także do

atrybutów.

Docstringi są bardzo wygodnym sposobem dodawania dokumentacji do kodu. Wymagają minimum wysiłku i wiedzy. To powoduje, że większość aplikacji Pythona tam, gdzie się ludzie śpieszą, powstaje własnie tą metodą.

Pydoc

Standardowo każda dystrybucja Pythona wyposażona jest w prosty skrypt: pydoc (plik pydoc.py leży w folderze lib razem z resztą modułów stadardowej biblioteki pythona. Pydoc umożliwia automatyczną generację dokumentacje do wszystkich modułów Pythona (zarówno tych z bibioteki standardowej jak i wszystkich jakie zainstalujemy).

Aby np. wyświetlić krótki manual na temat modułu md5 wystarczy że z poziomu shella wpiszemy np. pydoc modules md5

(Zamiast uruchamiać pydoc’a w shellu, można z poziomu interpretera wpisać: help(‘md5’). uzyskamy ten sam efekt co pydoc odpalony z shella. Odpowiada to mniej więcej uniksowym manualom.)

Można także wygenerować pliki html w stylu phpdoc’a albo po prostu odpalić na dowolnym porcie serwer www z dokumentacją do wszystkich modułów: pydoc -P 8080

Pod adresem http://localhost:8080 mamy witrynę z opisem wszystkich modułów Pythona zainstalowanych w systemie.

Istnieje znacznie więcej możliwości podpowiadania składni, uzupełniania kodu i innych udogodnień w Pythonie. Tu ograniczyłem się tylko do fragmentu, który nie ma odpowiednika w innych językach – docstringach – mechanizmowi tworzenia dokumentacji do kodu dla leniwych. :) To jedna z wielu doskonałych cech Pythona.

Posted in  | Tagi  | 1 comment

Dynamika otwartych klas: Ruby vs. Python

Opublikowane przez Jarosław Zabiełło Sat, 04 Mar 2006 05:36:00 GMT

Jedną z cech języka Ruby są tzw. otwarte klasy. Temat ten wywołuje mieszane uczucia i jest np. przedmiotem krytyki ze strony miłośników Pythona. Najczęściej jednak ta krytyka idzie w parze z nieznajomością Rubiego.

Np. w jednym z blogów spotkałem taki zarzut: zmiana działania klasy na której oparte są liczby całkowite może prowadzić do nieprzewidywalności w kodzie. Np.
class Fixnum
  def +(num)
    5
  end
end
print 2+2 # => 5 zamiast 4!

No i faktycznie, zmieniliśmy metodę operowania na liczbach całkowitych i Ruby nas oszukuje. Hmm, czy podobnej sytuacji nie ma w Pythonie? Ależ jest! Tylko że nie dotyczy podstawowych typów wbudowanych. Problem jednak jest ten sam. Ba, jest gorszy, co zaraz udowodnię. Ale wpierw prześledźmy różne scenariusze.

Podmiana metody w klasie

Załóżmy, że budujemy sobie klasę:
class Hello(object):
  def msg(self, msg):
    return "Hello " + msg
Miło i przyjemnie. Teraz mamy duży kod i sporo modułów z których co chwilę coś importujemy i na dodatek, pracujemy w kilka osób. Dyzio w jednym ze swych modułów stworzył klase o tej samej nazwie, ale o innej treści. Np.
class Hello(object):
  def msg(self, msg):
    return "Piss off " + msg

Teraz co się stanie jak moduł Dyzia zaimportuję do swojej przestrzeni nazw? Bez żadnego ostrzeżenia, klasa Dyzia wymieni moją klasę. Program może się nie posypie ale, klient uzyska zgoła odmienne przywitanie. :) Oczywiście może być gorzej, bo klasa Dyzia kompletnie usunie moją klasę co zaowocuje usunięciem wszystkich innych metod których się pracowicie natworzyłem.

Podobna sytuacja w Ruby nie owocuje kompletnym usunięciem wszystkich wcześniej stworzonych metod. Podmieniona zostanie tylko ta jedna, inna metoda. Cała reszta zostanie bez zmian.

Zabezpieczyć przed zmianą

Jak się zabezpieczyć przed modyfikacją klas? W Pythonie jest prosta odpowiedź: nie da się, bo Python nie posiada takich mechanizmów.

A co z Ruby? Okazuje się, że jest tu całkiem nieźle. Można dowolny obiekt (z klasą włącznie, bo klasa to też obiekt) zamrozić blokując możliwość jakiejkolwiek zmiany. Wystarczy więc, że wpiszę
Hello.freeze
Od tego momentu nikt nie będzie w stanie nic zmienić w mojej definicji. Można jedną instrukcją zablokować wszystkie klasy wbudowane jak ktoś bardzo tego chce:
ObjectSpace.each_object { |o| o.freeze if o.is_a? Class }

Zamrożenie obiektu jest nieodwracalne aż do końca działania programu. Nie da się żadnym sposobem za pomocą odpowiedniej komendy odmrozić raz zamrożony obiekt. Można co najwyżej sprawdzić czy już nie jest zamrożony (print obiekt.frozen?)

System Hooks

No dobrze, ale co w sytuacji kiedy nie chcemy zamrażać obiektu ale też nie chcemy aby gdzieś ktoś nam w nim coś zmienił bez wcześniejszego powiadomienia nas o tym? Tutaj z pomocą przychodzi coś co nazywa się System Hooks. W Ruby można bowiem świetnie śledzić co się dzieje z naszą klasą. Ruby potrafi wyłapywać następujące zdarzenia:

  • dodanie do klasy nowej metody dostępnej dla instancji klasy (Module#method_added)
  • usunięcie metody instancji z klasy (Module#method_removed)
  • wywołanie metody instancji, która nie istnieje (Module#method_undefined)
  • dodanie metody do singletona (Kernel.singleton_method_added)
  • usunięcie metody z singletona (Kernel.singleton_method_removed)
  • wywołanie nie istniejącej metody dla singletona (Kernel.singleton_method_undefineded)
  • stworzenie klasy potomnej (Class#inherited)
  • dodanie metod do klasy na drodze mixingu z modułów (Module#extend_object)
Przykład:
def info(msg)
  puts msg
end

class Hello
  def msg(msg)
    "Hello " + msg
  end
  def self.method_added(name)
    info "Dodano metode #{name}"
  end
  def self.inherited(name)
    info "Jakas klasa #{name} chce przeciazyc klase Hello"
  end
end

class Druga < Hello
end

class Hello    
  def nowy  
  end
end
Wynik:
Jakas klasa Druga chce przeciazyc klase Hello
Dodano metode nowy
Nie jest więc aż tak strasznie jak to co niektórzy trąbią. To prawda, że w Ruby można dodać metody do absolutnie każdej klasy (z wbudowanymi włącznie). Prawdą jest jednak także to, że można ten mechanizm w miarę kontrolować, śledzić, lub nawet zablokować. Zmiana metod klas wbudowanych pozwala na pisanie bardzo czytelnego kodu. Np. Ruby on Rails z tego sporo korzysta. Mogę samemu b. łatwo sobie zaimplementować coś podobnego.
class String
  def pluralize
    self + "s"
  end
  def singularize
    self[0...-1]
  end
end

puts "word".pluralize # => words
puts "books".singularize # => book

class Integer
  def kilobytes
    self * 1024
  end
end

puts 2.kilobytes # => 2048

Python (i żaden inny ze znanych mi języków) takich możliwości nie ma. A odnośnie tego, że Python nie pozwala zmienić definicji stringa czy integera (ogólnie typów wbudowanych) to mała to pociecha, skoro możne zmienić dowolnie inną klasę i to bez żadnego mechanizmu kontroli.

Posted in ,  | Tagi ,  | 5 comments

Pylons - czyli siła Myghty i wygoda RubyonRails

Opublikowane przez Jarosław Zabiełło Fri, 03 Mar 2006 23:51:00 GMT

Coraz bardziej ciekawie rozwija sie pythonowy framework do szybkiego budowania aplikacji internetowych: Pylons. W przeciwieństwie do TurboGears Pylons jest zbudowany na bazie Myghty a nie CherryPy. Myghty jest bardzo szybkim, stabilnym i dobrze napisanym frameworkiem. Pracuje jako cgi, mod_python, wielowątkowo lub wieloforkowo, dzięki WSGI. Posiada doskonały, elastyczny cache, obsługę AJAX’a, słowem prawie wszystko co trzeba dla współczesnej aplikacji internetowej. Jednak posiada za dużo cech które w branży pythonowej określa się jaki “perlish” (Myghty był wzorowany na perlowym Masonie)

I tu z pomocą przychodzi Pylons. Jest on czymś w rodzaju megaframeworku, czyli frameworku zbudowanym na bazie innego frameworku, w tym wypadku: Myghty. (Na podobnej zasadzie TurboGears jest megaframeworkiem opartym na CherryPy).

Pylons różni się jednak tym od TurboGears że jest oparty na frameworku znacznie bardziej solidnym i znacznie lepiej naśladuje framework bedący natchnieniem dla większości innych, czyli Ruby on Rails. Pylons czerpie pełnymi garściami z najlepszych pomysłów Railsów dodając do tego większą wydajność Pythona, elastyczność i prostotę składni oraz siłę jego bibliotek (np. Python ma doskonale zaimplementowaną obsługę Unicode, co jest w Ruby potraktowane trochę po macoszemu, a w PHP to w ogóle tragedia)

Zobaczmy przykładowo, co nam daje Pylons. Np. rozwiązywanie adresów. Adresy URL są rozwiązywane za pomoca bardzo ciekawej biblioteki Routes w sposób podobny do Railsów. Np. załóżmy że bieżący request zwraca następujący słownik: {‘controller’: ‘blog’, ‘action’: ‘view’, ‘id’: 2 (gdzie rozwiązywanie adresów url odbywa się wg definicji podobnej do Railsów: ’:controller/:action/:id’). Uzyskamy następujące wyniki:

url_for(id=4)                    =>  '/blog/view/4',
url_for(controller='/admin')     =>  '/admin',
url_for(controller='admin')      =>  '/admin/index/4'
url_for(action='edit')           =>  '/blog/post/4',
url_for(action='list', id=None)  =>  '/blog/list'

Pylons “przeportował do Pythona” całą masę użytecznych helperów znanych w Rails. Zajmuje się tym biblioteka RailsHelpers. Przykładowo wygląda to tak:

link_to("Delete this page", url(action="destroy", id=4), confirm="Are you sure?")
link_to("Help", url(action="help"), popup=True)
link_to("Busy loop", url(action="busy"), popup=['new_window', 'height=300,width=600'])
link_to("Destroy account", url(action="destroy"), confirm="Are you sure?", post => True)

Jaką to daje nam korzyść? Ogromną! Wyobraźmy sobie że chcemy zmienic sposób rozwiązywania adresów url. Co z milionami stron, które miały linki do różnych wewnętrznych części naszego serwisu? Koszmar! Trzeba ręcznie je poprawiać. Dzięki Pylon, nie musimy bezpośrednio wklepywać adres url, ale używamy do tego helperów. Zmiana zasad rozwiązywania adresów automatycznie poprawi adresy we wszystkich miejscach. Korzyść jest niewymierna!

RailsHelpers zawiera także więcej helperów. Np. można na nie zrzucić budowę formularzy.

Można też uzyskać efekty specjalne połączone z AJAX’em w identyczny sposób jak w Railsach. Pylons bowiem impelmentuje w Pythonie tą samą, znakomitą bibliotekę JavaScript: scriptaculous

Nie sposób nie wspomnieć także o interaktywnym debuggerze. To poprostu jest nowa jakość. Można interaktywnie prześledzić stan obiektów aplikacji internetowej. Świetny pomysł.

Jak dla mnie, Pylons staje się głównym rywalem Django do tytułu najlepszego frameworka dla języka Python. Jest znacznie bardziej elastyczny, posiada wysoką wydajność i możliwości Myghty oraz w łatwiejszy sposób rozwiązuje adresy url i ma wygodniejsze helpery, bo wzorowane na Ruby on Rails. Więcej na temat Pylons można śledzić także na stronie http://groovie.org/

Posted in ,  | Tagi ,  | 2 comments

Starsze wpisy: 1 2