Category: Python

Posted on in Technology, Python

Ohjelmoidessa on usein tarvetta luoda objekteja, joilla on joukko nimettyjä kenttiä. Python tarjoaa tähän useita vaihtoehtoja, joista dict-rakenne on ehkäpä yleisin, mutta hieman hankala kaikkine lainausmerkkeineen, varsinkin kenttiin myöhemmin viitatessa:

article = {'title':'Otsikko', 'body':'Leipäteksti'}
print article['title'], article['body']


Toinen vaihtoehto on toteuttaa pieni luokka, joka asettaa samat kentät attribuuteiksi. Mutta se on työlästä, koska joutuu kirjoittamaan __init__-metodin, jos haluaa luoda objekteja yhdellä rivillä:

class Article(object):
    def __init__(self, title, body):
        self.title = title
        self.body = body

article = Article(title='Otsikko', body='Leipäteksti')
print article.title, article.body


Kevyempi kompromissi on luoda vain tyhjä luokka, mutta silloin joutuu asettamaan attribuutit yksitellen:

class Article(object):
    pass

article = Article()
article.title = 'Otsikko'
article.body = 'Leipäteksti'
print article.title, article.body


Ehkäpä kaikkein näppärin vaihtoehto, jonka juuri bongasin Hacker Newsistä, on käyttää valmista collections.namedtuple-rakennetta. Namedtuplella määritelty luokka on optimoitu kevyeksi, staattiseksi container-objektiksi, jossa on kiinteä joukko kenttiä, joiden arvoja ei voi muuttaa. Objekteihin ei siis voi lisätä uusia kenttiä lennossa, ja objekteja luodessa kaikille kentille on annettava arvot:

from collections import namedtuple
Article = namedtuple('Article', ['title', 'body'])
article = Article(title='Otsikko', body='Leipäteksti')
print article.title, article.body


Jos myöhemmin huomaa, että rakenteesta on tarvetta tehdä dynaamisempi, niin namedtuple-määrittely on aika helppoa refaktoroida oikeaksi luokaksi. Sen voi myös periä, jolloin uusi luokka saa kyvyn lisätä attribuutteja, vaikka konstruktorissa onkin yhä pakko määritellä kaikki alkuperäiset:

class MyArticle(Article):
    pass

article = MyArticle(title='Otsikko', body='Leipäteksti')
article.unread = False
print article.title, article.body, article.unread


Oma periaatteeni on, että kuhunkin tilanteeseen kannattaa soveltaa siihen parhaiten soveltuvaa tekniikkaa. Jokaisesta asiasta ei tarvitse aina väkisin tehdä perinteistä luokkaa.

Posted on in Technology, Python

Useimpiin web-projekteihini liittyy Python-virtuaaliympäristö, joka on toteutettu virtualenvwrapperilla. Huomasin, että siirtyessäni työskentelemään projektin parissa kirjoitan aina kaksi komentoa tähän tyyliin:

$ cd myproject
$ workon myproject

Rupesin miettimään, voisiko tämän automatisoida niin, että kun siirryn myproject-hakemistoon, niin virtuaaliympäristö aktivoitaisiin automaattisesti. Ja tähän löytyykin helppo ratkaisu, kunhan käyttää bash-shelliä (oletus Mac OS X:ssä sekä useimmissa Linuxeissa) ja asettaa sille PROMPT_COMMAND-muuttujan. Tämän muuttujan sisältämä komento ajetaan joka kerta, kun bash näyttää komentokehotteen.

Tässä esimerkki:

export PROMPT_COMMAND='[ -z "$VIRTUAL_ENV" -a -n "$WORKON_HOME" -a -d $WORKON_HOME/$(basename $PWD) ] && workon $(basename $PWD)'

Komento tarkistaa ensin, onko $VIRTUAL_ENV jo asetettu, eli ollaanko jo virtuaaliympäristössä. Jos ei olla, niin tarkistetaan, onko virtualenvwrapper asennettu ($WORKON_HOME) ja löytyykö siitä samanniminen ympäristö kuin tämänhetkinen hakemisto. Jos löytyy, niin ajetaan workon-skripti, joka aktivoi kyseisen virtuaaliympäristön. Tällöin $VIRTUAL_ENV asettuu automaattisesti, eikä komentoa enää ajella uudelleen.

PROMPT_COMMANDin kanssa ei kannata kikkailla liikaa, ettei shellin käyttö hidastu turhaan jokaisella enterinpainalluksella. Tätäkin esimerkkiä voisi varmaan optimoida lisää, mutta tuntuisi se kuitenkin toimivan.

Posted on in Technology, Python

Eräs usein toistuva ohjelmointiprobleema on laskea yhteen usean eri dictionary-objektin sisältämien avainarvoparien summat.

Lähtökohtana voivat olla vaikkapa tällaiset objektit:

values1 = {'small':10, 'medium':20, 'large':30}
values2 = {'medium':50}
values3 = {'large':100}

Miten näistä saadaan helpoiten yhdistettyä yksi objekti, joka sisältää nämä yhteenlasketut arvot?

{'small':10, 'medium':70, 'large':130}

Python 2.7 tarjoaa tähän kätevän Counter-luokan, jolla voidaan suorittaa kyseiset laskutoimitukset suoraan +-operaatioina:

from collections import Counter
print dict(Counter(values1) + Counter(values2) + Counter(values3))
{'large': 130, 'medium': 70, 'small': 10}

Huomaa, että Counter on dict-luokan aliluokka, joten sitä voi halutessaan käyttää sellaisenaan tai helposti muuttaa takaisin dict-luokaksi.

Jos dictionaryja on vaihteleva määrä, ne voi laskea yhteen myös reduce-funktiolla näin:

all_values = [values1, values2, values3]
print dict(reduce(lambda x,y:x+y, [Counter(values) for values in all_values]))
{'small': 10, 'large': 130, 'medium': 70}

Posted on in Technology, Python

Lua is a popular embedded scripting language used by many tools and games such as World of Warcraft. There is also a Python port called Lunatic-Python. It wraps around liblua (the C library) and allows you to execute Lua code in Python and Python code in Lua.

I've created a fork of Lunatic-Python in:

https://github.com/kennu/Lunatic-Python

This fork allows you to create multiple Lua states, each of which has its own set of global variables.

Previously, in Python you would execute Lua with a global state like this:

import lua
lua.globals()['msg'] = {'hello':'world'}
lua.execute("print(msg.hello)")

Now you can create multiple independent states using the new_state() function:

import lua
s1 = lua.new_state()
s2 = lua.new_state()
s1.globals()['msg'] = {'hello':'world'}
s2.globals()['msg'] = {'hello':'foobar'}
s1.execute("print(msg.hello)")
s2.execute("print(msg.hello)")

The purpose of this is that you can safely create a new, clean state when you're executing Lua in a Python function, and you don't have to worry about the global state overlapping with some other function or thread.

Please consider this fork experimental for now. It passes the test_lua.py tests but it may still contain memory leaks or other problems. Also, I have not tested it for embedding Python into Lua at all.

Posted on in Technology, MongoDB, Python, Django

Lähetin tänään patchit kahteen Django-projektiin niiden saattamiseksi toimimaan MongoDB:n kanssa:

github.com/kennu/Django-facebook
bitbucket.org/kennu/django-registration

Django-facebook on moduuli, jonka avulla Djangon käyttäjätietokannan saa helposti integroitua Facebookiin. Facebook-käyttäjä yhdistetään automaattisesti aiempaan Djangon User-objektiin, jolla on sama sähköpostiosoite, tai sitten hänelle luodaan uusi User-objekti. Samalla erilliseen UserProfile-objektiin tallentuu Facebook-käyttäjän ID ja access-token, joita voi sitten käyttää Graph APIssa.

Django-registration puolestaan tarjoaa valmiit toiminnot Django-käyttäjien rekisteröintiin ja sähköpostiaktivointiin. Django-facebook käyttää sitä apunaan uusien käyttäjien luomisessa.

Tekemäni patchit korjaavat pieniä epäyhteensopivuuksia päivämäärien käsittelyssä ja JOIN-kyselyissä siten, että ne toimivat yhteen django-mongodb-enginen kanssa.

Posted on in Technology, Python, Cloud

I just created my own fork of Boto (the Python API for Amazon AWS):

https://github.com/kennu/boto

Mainly I added support for adding and removing Alias Resource Record Sets, which integrate Route 53 to Elastic Load-Balancing. This allow you to use root domains (example.com without the www prefix) with load balanced services.

The shell command to add an ELB alias is something like:

route53 add_alias xxxx example.com. A yyyy elb-name.elb.amazonaws.com.

Where xxxx is the Route 53 Zone ID and yyyy is the Zone ID of the load balancer (shown in AWS EC2 Console).

Posted on in Technology, Python, Django

Varnish on tehokas välimuistipalvelin, joka pitää taustalla toimivan varsinaisen HTTP-palvelimen sivuja muistissa ja palvelee ne suoraan sieltä. Sen avulla on helppo nopeuttaa esimerkiksi Djangon toimintaa huomattavasti, kun jokaista HTTP-pyyntöä ei tarvitse käsitellä yhä uudelleen Python-koodissa.

Ongelmaksi muodostuu se, että kun Django-saitin sisältöä muokataan, Varnish tarjoilee edelleen vanhaa sisältöä N minuutin ajan. Jos sisällön haluaa päivittyvän heti, Varnishin välimuisti täytyy tyhjentää.

Tähän tarkoitukseen löytyy kaksi GitHub-projektia nimeltään python-varnish ja django-varnish. Valitettavasti näissä projekteissa on muutamia puutteita, joiden takia ne eivät soveltuneet omaan käyttööni. Tämän vuoksi tein molemmista omat forkit, jotka löytyvät osoitteista:

Tein seuraavanlaisia parannuksia:

  • python-varnish osaa autentikoitua shared secretillä Varnishiin
  • django-varnishiin voi konfiguroida shared secretin settings.py:hyn
  • django-varnish tukee VARNISH_GLOBAL_WATCHED_MODELS-asetusta, joka on lista Djangon tietomalleista, joiden muokkaaminen tyhjentää Varnishin välimuistin kokonaan