Olen käynyt aiemmassa blogikirjoituksessa keskustelua PHP:n ja Pythonin eroista koodin tiiviyden suhteen. Siellä heittämäni esimerkit eivät olleet parhaita mahdollisia, joten ajattelin kirjoittaa tähän erikseen muutamista eroista, joiden uskon oikeasti tekevän Pythonista PHP:tä paremman kielen. Kirjoitan otsikot englanniksi, kun en oikein tunne näiden käsitteiden suomenkielisiä nimiä.

List comprehensionit

Web-sovellusohjelmoinnissa on aika yleinen tilanne luupata läpi taulukko, suorittaa taulukon jokaiselle jäsenelle jokin operaatio, ja tallentaa tulokset uuteen taulukkoon. PHP:ssä voisi ottaa esimerkin, jossa artikkeleista kerätään lista niiden kategorioista pieninä kirjaimina:

$categories = array();
foreach ($articles as $article) {
    if ($article->published) {
        foreach ($article->categories as $category) {
            $categories[] = strtolower($category)];
        }
    }
}
$categories = array_unique($categories);

Pythonissa list comprehensioneita käyttäen yhdellä koodirivillä esimerkiksi näin [Puuttuva ehto ja lower() lisätty]:

set(reduce(lambda x,y: x+y, [[c.lower() for c in article.categories] for article in articles if article.published]))

Itseäni tässä Python-versiossa viehättää eniten se, että rakenne on funktionaalinen eikä imperatiivinen. Tietorakenteita ei tökitä ja sörkitä vaan ne muodostetaan tietyllä säännöllä. Tämä tarkoittaa, että koodista poistuu useita potentiaalisia bugiriskejä taulukkojen alustamisessa, luuppaamisessa ja muuttamisessa. Mitä enemmän koodia, sitä helpommin näihin tulee kirjoitusvirheitä tai muutetaan vahingossa väärää taulukkoa.

Tommi F. varmaan näyttää, miten tuo esimerkki tehdään PHP:llä oikein? ;-)

Keyword-argumentit

Pythonin keyword-argumentit ovat parhaimmillaan kun suunnitellaan API-rajapintoja, joissa pitää välittää iso määrä erilaisia datakenttiä, joista osa on vapaaehtoisia. PHP:ssä tämä pitää usein tehdä assosiatiivisilla taulukoilla tähän tapaan:

function createArticle($fields) {
    if (!array_key_exists('published', $fields)) $fields['published'] = true;
    if (!array_key_exists('body', $fields)) $fields['body'] = '';
    if (!array_key_exists('author', $fields)) $fields['author'] = '';
    INSERT INTO ... $fields['published'], $fields['title'], $fields['body'], $fields['author']
}

createArticle(array('title' => 'Otsikko', 'author' => 'kennu'));

Pythonissa rajapinnasta saadaan paljon selkeämmin määritelty:

def create_article(title, body='', published=True, author=''):
    INSERT INTO ... published, title, body, author

create_article('Otsikko', author='kennu')

Keyword-argumentteja voi hyödyntää vielä monilla muillakin tavoilla. Erityisen mukavaa on, että niitä voi muokata ja välittää eteenpäin toisille funktioille:

def create_article(title, body='', published=True, author='', **kwargs):
    del(kwargs['unwanted_field'])
    handle_extra_fields(**kwargs)

Näin pystyy rakentamaan hyvin joustavia mekanismeja jatkuvasti muuttuvien tietorakenteiden käsittelyyn, ja toisaalta on helppo kutsua olemassaolevia funktioita muuttamalla argumentteja lennossa tarpeen mukaan.

Meta-propertyt

Halusin määritellä eräässä webbiprojektissa muuttujan nimeltä request.profile, joka palauttaa käyttäjän profiiliobjektin. Halusin kuitenkin, ettei profiilia ladata turhaan tietokannasta, jos kyseistä muuttujaa ei koskaan käytetä missään. Tämä tilanne toteutuu silloin, jos webbisivun kaikki ne osat tulevat välimuistista, joissa profiilia olisi tarvittu. (Sivunmuodostus on hyvin modulaarinen, osa voi tulla välimuistista ja osa ei.)

Ratkaisuni oli suurin piirtein tällainen:

class LazyProfileDescriptor(object):
    """Class for lazy loading of request.profile."""
    def __init__(self, request):
        self._lazy_request = request
        self._lazy_profile = None
    def __getattribute__(self, name):
        """Load profile when descriptor is first accessed."""
        if name.startswith('_lazy_'):
            return object.__getattribute__(self, name)
        if self._lazy_profile is None and self._lazy_request is not None:
            self._lazy_profile = self._lazy_request.user.get_profile()
        return getattr(self._lazy_profile, name)

request.profile = LazyProfileDescriptor(request)

On mahdollista, että koodi vielä sievenisi tuosta, mutta tämä mielestäni kuitenkin demonstroi Pythonin meta-ohjelmoinnin tarjoamia mahdollisuuksia. Tällaisen määrittelyn jälkeen kaikkialla muualla koodissa voi huoletta kutsua request.profile-muuttujaa normaalisti, ja se latautuu sitten tietokannasta vain jos on tarvis. LazyProfileDescriptorista voisi myös melko helposti tehdä geneerisen luokan, joka osaa ladata mitä tahansa tietokannasta tarvittaessa. Latauslogiikka annettaisiin sille konstruktorissa lambda-funktiona.

Metaohjelmointia voisi käsitellä vielä laajemmin meta-classien ja deskriptorien osalta, mutta niistä alkaa olla jo vaikea keksiä lyhyitä havainnollisia esimerkkejä. Django on itsessään loistava esimerkki siitä, miten näitä ominaisuuksia hyödyntäen on luotu kirjasto, jota sovellusohjelmoijan on yksinkertaista käyttää, mutta jossa on hyvin monipuolinen toimintalogiikka taustalla.

Published 19.11.2009