Visualizzazione post con etichetta python. Mostra tutti i post
Visualizzazione post con etichetta python. Mostra tutti i post

03 novembre, 2007

Tip: vim e indentazione del codice python

Spesso il codice python che troviamo in rete è indentato usando 4 spazi piuttosto che ad esempio un carattere di tabulazione. Io generalmente preferisco la tabulazione agli spazi. Convertire dall'una all'altra indentazione è in realtà piuttosto semplice, così semplice che ci ho pensato davvero tardi ouch ;)
Basta infatti una semplice sostituzione con vim:


:%s/    /\t/g


Ecco, tutto qui :)

14 maggio, 2007

Python Cairo e le glass window

Chi non conosce il lavoro di njpatel? Ormai i suoi avant window navigator e affinity sono noti ai più. Le sue idee sono innovative è apprezzate da molti bloggers. In questo post non voglio solo esaltare le sue produzioni (anche perché arriverei molto in ritardo in questo) ma voglio spingermi un po oltre per spiegare il modo di operare per ottenere risultati visivi simili ma utilizzando il nostro amato pitone.

A chi non sapesse a cosa mi riferisco, rimando a questo post sul blog di njpatel. Notate le trasperenze della finestra e gli altri widget inseriti normalmente al suo interno.

Come faccio ad ottenere un risultato simile in python?


#!/usr/bin/env python

import gtk
import cairo

if gtk.pygtk_version < (2, 10, 0):
print 'Questo esempio ha bisogno delle PyGtk >= 2.10'
raise SystemExit

def hex2float(hex):
ret = []
for i in range(4):
ic = int(hex[i])
ret.append( ic / 255.0 )
return ret


class TransparentWindow(gtk.Window):
__gsignals__ = {
'expose-event': 'override',
'screen-changed': 'override',
}

def __init__(self):
gtk.Window.__init__(self)

# Indichiamo alle GTK che vogliammo disegnare noi lo sfondo.
self.set_app_paintable(True)

# non vogliamo le decorazioni del window manager
self.set_decorated(False)

# usiamo il segnale button-press-event per permettere
# il "click e trascina" sulla finestra. Ricordiamo
# che non stiamo utilizzando i classici decoratori
# del window manager e non gestendo qeusto segnale
# non sarebbe possibile spostare la finestra
self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.connect('button-press-event', self.on_button_press)

# Inizializziamo lo schermo
self.do_screen_changed()

def on_button_press(self, widget, event):
self.begin_move_drag(
event.button,
int(event.x_root),
int(event.y_root),
event.time)

def render_rect(self, cr, x, y, w, h, o):
# Crea un rettangolo con i bordi arrotondati
x0 = x
y0 = y
rect_width = w
rect_height = h
radius = 10 + o

x1 = x0 + rect_width
y1 = y0 + rect_height
cr.move_to(x0, y0 + radius)
cr.curve_to(x0, y0, x0, y0, x0 + radius, y0)
cr.line_to(x1 - radius, y0)
cr.curve_to(x1, y0, x1, y0, x1, y0 + radius)
cr.line_to(x1 , y1)
cr.line_to (x0 , y1)
cr.close_path()

def do_expose_event(self, event):
cr = self.window.cairo_create()

if self.supports_alpha:
cr.set_source_rgba(1.0, 1.0, 1.0, 0.0)
else:
cr.set_source_rgb(1.0, 1.0, 1.0)

cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()


(width, height) = self.get_size()
cr.move_to(0, 0)
cr.set_line_width(1.0)

cr.set_operator(cairo.OPERATOR_OVER)

pat = cairo.LinearGradient(0.0, 0.0, 0.0, height)

ex_list = [0xA1, 0xA8, 0xBB, 0xEC]
col = hex2float(ex_list)
pat.add_color_stop_rgba(0.0, col[0], col[1], col[2], col[3])

ex_list = [0x14, 0x1E, 0x3C, 0xF3]
col = hex2float(ex_list)
pat.add_color_stop_rgba(1.0, col[0], col[1], col[2], col[3])

self.render_rect(cr, 0, 0, width, height, 10)
cr.set_source(pat)
cr.fill()

# bordo luminoso
ex_list = [0xFF, 0xFF, 0xFF, 0x4e]
col = hex2float(ex_list)
cr.set_source_rgba(col[0], col[1], col[2], col[3])
self.render_rect(cr, 1.5, 1.5, width - 3 , height - 3, 10)
cr.stroke()

# bordo
ex_list = [0x00, 0x15, 0x1F, 0xe0]
col = hex2float(ex_list)
cr.set_source_rgba(col[0], col[1], col[2], col[3])
self.render_rect(cr, 0.5, 0.5, width - 1 , height - 1, 10)
cr.stroke()

ex_list = [0xFF, 0xFF, 0xFF, 0xFF]
col = hex2float(ex_list)
cr.set_source_rgba(col[0], col[1], col[2], col[3])
self.render_rect(cr, 0, 0, width , height, 10)
cr.stroke()

pat = cairo.LinearGradient(0.0, 0.0, 0.0, height)
cr.set_source(pat)
ex_list = [0xFF, 0xFF, 0xFF, 0xbb]
col = hex2float(ex_list)
pat.add_color_stop_rgba(0.0, col[0], col[1], col[2], col[3])

ex_list = [0x00, 0x00, 0x10, 0xaa]
col = hex2float(ex_list)
pat.add_color_stop_rgba(0.2, col[0], col[1], col[2], col[3])
self.render_rect(cr, 0, 0, width, 20, 10)
cr.fill()

# chiediamo esplicitamente ai figli di disegnarsi
# Se non lo facessimo non sarebbero visualizzati
# i widgets aggiunti alla TransparentWindow
children = self.get_children()
for c in children:
self.propagate_expose(c, event)

def do_screen_changed(self, old_screen=None):
screen = self.get_screen()
if self.is_composited():
print 'Il tuo schermo supporta il canale alpha!'
colormap = screen.get_rgba_colormap()
self.supports_alpha = True
else:
print 'Il tuo schermo non supporta il canale alpha! Il composite\
manager e\' attivo?'
colormap = screen.get_rgb_colormap()
self.supports_alpha = False
self.set_colormap(colormap)

class CoolAlpha:
def __init__(self):
window = TransparentWindow()
window.resize(400, 400)
eb = gtk.EventBox()
eb.set_visible_window(False)
eb.set_border_width(12)
window.add(eb)

main_box = gtk.VBox(False, 12)
eb.add(main_box)

hbox = gtk.HBox(False, 6)
main_box.pack_start(hbox, False)

button = gtk.Button()
button.set_relief(gtk.RELIEF_NONE)
button.connect("clicked", gtk.main_quit)
icon_box = gtk.HBox(False, 0)

image = gtk.Image()
image.set_from_stock(gtk.STOCK_CLOSE,
gtk.ICON_SIZE_MENU)
icon_box.pack_start(image, False)
button.add(icon_box)
hbox.pack_end(button, False)

sw = gtk.ScrolledWindow()
sw.set_property("border-width", 20)
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.textview = gtk.TextView()
self.textview.set_editable(False)
self.textview.set_cursor_visible(False)
sw.add(self.textview)
main_box.pack_start(sw, True)

window.show_all()

def set_text(self, text):
buf = self.textview.get_buffer()
buf.set_text(text)

if __name__ == '__main__':
a = CoolAlpha()
a.set_text("prova")
gtk.main()

Questo esempio è in gran parte una traduzione del codice in linguaggio c di affinity di njpatel con qualche piccolo ritocco da parte mia come l'inclusione del pulsante di chiusura in alto a destra e la textview centrale. Eseguendo l'esempio otterrete:

In genere le gtk non vengono usate per costruire finestre troppo "esotiche" e c'è un buon motivo per questo, ovvero mantenere la consistenza delle applicazioni sul desktop. Quindi nonostante queste finestre possono apparire molto belle a vedersi, il mio consiglio e di non abusarne ma utilizzarle in modo da ottenere una buona integrazione con il resto del desktop per non fare apparire la nostra applicazione come un "alieno".

10 maggio, 2007

Baby e ybab: uno script python per usare i dizionari babylon

E' da tanto che non scrivo? Lo so ma fra qualche giorno se tutto procede come previsto dovrei laurearmi quindi il tempo da dedicare al blog è praticamente inesistente. Appena tutto sarà finito tornerò a sviluppare cGmail e a postarvi gli studi su python e tutto il resto. Per ora, per non lasciarvi proprio a bocca asciutta, vi propongo un piccolo "bocconcino" che ho sviluppato qualche tempo fa e che potrebbe risultare utile (o quantomeno potrebbe essere uno spunto) a qualcuno di voi. Si tratta di un semplice script (indovinate in che linguaggio? . . . Bravi :) ) che interroga il database dei dizionari babylon, si presenta come Firefox 2 e poi interpreta l'html per tirare fuori le informazioni sul lemma ricercato.

Il codice python


#! /usr/bin/env python
import urllib2
import re
import sys
from sgmllib import SGMLParser

red='\033[0;31m'
RED='\033[1;31m'
blue='\033[0;34m'
BLUE='\033[1;34m'
cyan='\033[0;36m'
CYAN='\033[1;36m'
NC='\033[0m'

ROWLEN = 60

class Parser(SGMLParser):
def __init__(self):
SGMLParser.__init__(self)
self.must_print = False

def reset(self):
SGMLParser.reset(self)

def write(self, data):
sys.stdout.write(data)
sys.stdout.flush()

def start_div(self, attrs):
for attr, value in attrs:
if attr == "class":
if value == "term":
self.write(RED + '\n')
self.must_print = True
if value == "definition":
self.must_print = True

def end_div(self):
self.must_print = False
self.write(NC)

def handle_data(self, data):
if not self.must_print: return
if data.strip() == "": return
tmp = data.strip()
tmp = data.replace('\n', '')
count = len(tmp)
if count > ROWLEN:
pieces = count / ROWLEN
for i in range(pieces):
l = ROWLEN * i
u = ROWLEN * (i + 1)
self.write(tmp[l:u] + '\n')
else:
self.write(tmp + '\n')



def http_req(url, data):
req = urllib2.Request(url, data)
req.add_header('User-Agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.4) Gecko/20061201 Firefox/2.0.0.4 (Ubuntu-feisty)')
req.add_header('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7')
req.add_header('Accept', 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5')
stream = urllib2.urlopen(req)
out = stream.read()
stream.close()
return out


def usage():
print "Usage: baby parola\n";

def search(word, lang):

out = http_req('http://info.babylon.com/onlinebox.cgi',
'rt=ol&tid=pop&uil=EN&cid=CD1&term='+word+'&tl='+lang)
return out


if __name__ == "__main__":
import os
args = sys.argv[1:]
if args[0] == None:
usage()
sys.exit(1)
word = args[0]
out = ""
appname = os.path.basename(sys.argv[0])
print appname
if appname == "baby":
out = search(word, "EN")
elif appname == "ybab":
out = search(word, "IT")
p = Parser()
p.feed(out)
p.close()

Come lo installo?
Copiate ed incollate il codice in un file che chiamerete baby (Attenzione: il nome è importante). Date i permessi di esecuzione al file e copiatelo in una directory inclusa nel vostro path (ad esempio /usr/local/bin). A questo punto create un link simbolico al file con nome ybab. Esempio di tutti i passaggi (supponiamo che avete salvato lo script di cui sopra su un file di nome baby sul vostro desktop):

sudo cp ~/Desktop/baby /usr/local/bin
sudo chmod +x /usr/local/bin/baby
sudo ln -s /usr/local/bin/baby /usr/local/bin/ybab

Come si usa?
L'uso è semplicissimo. Invocando baby [lemma_da_cerare] verrà considerata come lingua target l'inglese, invocando invece ybab [lemma_da_cercare] verrà considerato come target l'italiano.

Esempio:

$ ybab glad
ybab

glad
agg.
felice, contento, lieto; gioioso
s.
gladiolo (fiore)

GLAD
CONTENTO. LIETO. FELICE. ALLIETARE. RALLEGRARE. GLADIOL
Purtroppo per alcuni lemmi l'output non è così pulito (ci sarebbe da migliorare il parser, suggerimenti sono ben accetti. Io non mi ci sono dedicato più di tanto)

Come funziona
Le informazioni che seguono sono piuttosto tecniche. Se vi interessa solo il programmino e non sapere come funziona potete tranquillamente evitare di proseguire nella lettura.

Lo script utilizza la urllib2 di python per fare una semplice richiesta al sito di babylon. Per conoscere l'esatto URL da interrogare e i parametri da passargli è necessario studiarsi un po il sito, conoscere un minimo di programmazione web, sapere come funzionano le form ed avere un po di pazienza. Una volta noto il meccanismo di interrogazione utilizzato dal sito in questione, il resto è "semplice". In questo caso l'output della richiesta http, viene "parsato" (passatemi il termine) dall'apposito parser che utilizza la libreria sgmllib. Un aspetto interessante dello script è il modo che utilizza per cambiare la sua funzionalità (in questo caso la lingua target). Ricordiamo che il primo parametro passato ad un programma è sempre il suo nome. Quindi distinguendo il nome del primo parametro è possibile effettuare operazioni diverse
. In questo caso se il programma viene invocato come baby viene utilizzata la lingua inglese, mentre invocato come ybab quella italiana.

29 aprile, 2007

pygtk: un About Dialog con link cliccabile

Creare un about dialog con link di riferimento cliccabile è una di quelle cose che vediamo in molte applicazioni ma per la quale non è semplice trovare documentazione a riguardo.

Scoprire come fare per me non è stato semplicissimo e quindi ho pensato che una soluzione pronta potrebbe essere di aiuto a qualcuno.

Cosa stiamo per realizzare
Ciò che ci apprestiamo a realizzare è un dialogo di informazione come quello che potete vedere in figura:


Da notarsi che il link è cliccabile e cliccandoci sopra verrà aperto un browser che vi porterà guarda caso proprio a quell'indirizzo :) Le librerie gtk (e i rispettivi bindings per python) mettono a disposizione un apposito widget per creare i dialoghi di informazione. Il widget in questione permette di settare facilmente il nome dell'applicazione, la versione, gli autori, la licenza, i traduttori e altre cosette interessanti. Stranamente però non permette di impostare in modo altrettanto semplice un url che risponda al click del mouse. Per farlo è necessario utilizzare la funzione gtk.about_dialog_set_url_hook e indicare una callback da richiamare quando l'utente clicca sul link. La callback in questione dovrà poi occuparsi di creare un nuovo processo per lanciare il browser.


Il codice python



import gtk
import subprocess

def on_url(d, link, data):
subprocess.Popen(["firefox", "http://linubuntu.blogspot.com"])

gtk.about_dialog_set_url_hook(on_url, None)

def create_dialog():
dlg = gtk.AboutDialog()
dlg.set_version("0.1")
dlg.set_name("app")
dlg.set_license("Puoi fare di questo software quello che ti pare")
dlg.set_authors(["redgun"])
dlg.set_website("http://linubuntu.blogspot.com")
def close(w, res):
if res == gtk.RESPONSE_CANCEL:
w.hide()
dlg.connect("response", close)
return dlg

if __name__ == "__main__":
dlg = create_dialog()
dlg.run()


Ricordate che la finestra di dialogo di suo non risponde al click sul pulsante chiudi ma sarà necessario impostare una funzione di callback per il segnale response come nel codice.

19 aprile, 2007

Un modulo python per gestire le chiavi in gconf

Aggiornamento: ho aggiornato il modulo che è ora una classe ed include il metodo clean_section


L'ambiente desktop gnome mette a disposizione un ottimo sistema per memorizzare le impostazioni delle sue applicazioni: gconf.

Come funziona gconf?
Gconf è un sistema per la memorizzazione delle configurazioni basato su XML. La configurazione è contenuta in un albero ed ogni applicazione può costruire un suo sottoalbero nel quale memorizzare le proprie impostazioni. Gconf è composto da un demone (gconfd), che si occupa di notificare le applicazioni di eventuali modifiche alla loro configurazione (magari fatte da una applicazione esterna come gconf-editor) e di mantenere una cache dei valori in modo che le applicazioni non siano costrette a dover rifare il parsing del file XML ogni volta, e da un insieme di librerie che rendono semplice l'accesso all'albero gconf.


Come posso accedere/creare chiavi gconf con python?
Usare gconf da python è veramente semplice anche se un minimo di astrazione in più non fa certo male. Per questo vi voglio riportare un piccolo modulo python che permette di comprendere l'uso di gconf oltre che semplificare il processo di creazione/accesso/modifica delle chiavi.

Il codice python


import gtk
import gconf


class MyGconf:

def __init__(self, appname):
self.basedir = "/apps/" + appname
self.client = gconf.client_get_default()

def add_section(self, name, keys):
"""
Aggiunge una sottodirectory a self.basedir e la riempie
con le chiavi specificate nel dizionario keys
"""
dir = self.basedir + "/" + name + "/"
for key, value in keys.iteritems():
path = dir + key
self.client.set_string(path, value)

def clean_section(self, name):
"""
cosa potrebbe mai fare questo metodo :)
"""
dir = self.basedir + "/" + name
self.client.recursive_unset(dir, 0)

def get_sections(self):
"""
Ritorna una tupla contenente l'elenco delle sezioni
contenute in self.basedir
"""
return self.client.all_dirs(self.basedir)

def get_key(self, name):
"""
Ritorna il valore di una specifica chiave.
name deve essere un percorso relativo a self.basedir
"""
path = self.basedir + "/" + name
return self.client.get_string(path)

def set_key(self, name, value):
"""
Imposta il valore della chiave name a value
"""
path = self.basedir + "/" + name
self.client.set_string(path, value)




if __name__ == "__main__":
mg = MyGconf("myapp")
params = {
"test": "test value",
"test2" : "test2 value"
}
mg.add_section("mydir", params)
mg.add_section("mydir2", {"test": "value"})

print mg.get_key("mydir/test")
print mg.get_sections()
mg.set_key("mydir/test", "new value")
print mg.get_key("mydir/test")

mg.remove_section("mydir")


Il codice dovrebbe essere autoescplicativo ;)

14 aprile, 2007

Una classe per le notifiche con python e dbus: parte seconda

Qulche tempo fa in questo post avevo spiegato come è possibile generare una notifica utilizzando python e dbus. Il commento di AS in quel post mi ha dato l'ispirazione per approfondire le conoscenze su dbus e per creare questo nuovo post con una versione più evoluta della classe python per le notifiche (vi suggerisco la lettura del post precedente prima).

La nuova bestiola mostra un esempio di come sia possibile aggiungere funzionalità alle notifiche con l'inclusione di pulsanti.

L'esempio chiarisce inoltre come sia possibile posizionare la notifica in un punto arbitrario del desktop.

Il codice python


import dbus
from dbus.mainloop.glib import DBusGMainLoop

ICON = "file:///home/redgun/python/planimo/data/icons/planimo48.png"

class Notifier:
def __init__(self):
dbus_loop = DBusGMainLoop()
self.session_bus = dbus.SessionBus(mainloop = dbus_loop)
obj = self.session_bus.get_object("org.freedesktop.Notifications",
"/org/freedesktop/Notifications")
self.notif = dbus.Interface(obj, "org.freedesktop.Notifications")

self.notif.connect_to_signal("ActionInvoked", self.action_cb)

def action_cb(self, id, act):
print act

def notify(self, title, message, iconfile = ICON, time = 3000):
hints = {}
hints['x'] = 1200
hints['y'] = 50
actions = ["a1", "Azione1", "a2", "Azione2"]
try:
self.notif.Notify("Notification",
dbus.UInt32(0), iconfile, title, message,
actions, hints, dbus.Int32(time), dbus.UInt32(0))
except:
try:
self.notif.Notify("Notification",
dbus.UInt32(0), iconfile, title, message,
actions, hints, dbus.Int32(time))
except Exception, detail:
print detail

if __name__ == "__main__":
n = Notifier()
n.notify("Title", "message <b>bold style</b>", time=10000)

import gobject
loop = gobject.MainLoop()
loop.run()


Il risultato sarà il seguente:



Da notarsi che i pulsanti sono cliccabili e le azioni possono essere gestite all'interno del metodo action_cb. La variabile hints, invece ci permette di impostare le coordinate della notifica sul desktop. Le azioni vengono specificate in una lista dove gli elementi pari rappresentano i nomi delle azioni che andranno a comparire sui pulsanti, mentre quelli dispari rappresentano il valore che verrà passato alla funzione di callback. In questo caso quindi, quando premiamo il pulsante Azione1 la funzione action_cb riceverà nella variabile act il valore a1.

03 aprile, 2007

Catturiamo il SIGTERM con python

Scrivendo una applicazione a volte è necessario compiere delle operazioni di "pulizia" prima che questa venga chiusa. A volte è necessario compierle anche se l'applicazione viene uccisa con kill.

Il comando kill

Il comando Kill serve per inviare dei segnali ad un processo. Utilizzato senza opzioni e con parametro un pid di un processo (che possiamo trovare utilizzando il comando ps), kill invia a tale processo il segnale SIGTERM che in genere indica al processo di terminare le sue operazioni. Esistono molti altri segnali ma in questo post ci occuperemo solo del SIGTERM.

Il modulo signal

Per la gestione dei segnali python mette a disposizione il modulo signal. Una delle funzioni più importanti presenti in questo modulo è la signal(). Questa funzione permette di registrare un handler per un particolare segnale.

Esempio


import signal
import sys
import time

def sigterm_handler(signum, frame):
print "SIGTERM catturato"
sys.exit(0)

signal.signal(signal.SIGTERM, sigterm_handler)

while (1):
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(1)

Questo esempio continua a stampare "." fin quando non cattura il segnale SIGTERM inviato con una kill pid. A questo punto stampa il messaggio che indica che il segnale è stato catturato ed esce

11 marzo, 2007

Una classe per le notifiche con python e dbus

Ogni tanto trovo un minuto per migliorare il mio progetto planimo ed imparo qualcosa di nuovo su python e le varie estensioni. In un prossimo post vi annuncerò le grandi novità del progetto ma per ora vi voglio presentare un metodo semplice per far apparire le notifiche sul nostro desktop (solo unixes, no Windows). La notifica sarà simile a questa:


La notifica apparirà in basso a destra sul desktop per un periodo di tempo impostabile.
Dbus è un sistema per la comunicazione tra processi. In pratica si sfrutterà dbus per comunicare con il demone delle notifiche.

Il codice python



import dbus

ICON = "file:///home/redgun/python/planimo/data/icons/planimo48.png"

class Notifier:
def __init__(self):
self.session_bus = dbus.SessionBus()
obj = self.session_bus.get_object("org.freedesktop.Notifications",
"/org/freedesktop/Notifications")
self.notif = dbus.Interface(obj, "org.freedesktop.Notifications")

def notify(self, title, message, iconfile = ICON, time = 3000):
try:
self.notif.Notify("Notification",
dbus.UInt32(0), iconfile, title, message,
[], [], dbus.Int32(time), dbus.UInt32(0))
except:
try:
self.notif.Notify("Notification",
dbus.UInt32(0), iconfile, title, message,
[], [], dbus.Int32(time))
except Exception, detail:
print detail

if __name__ == "__main__":
n = Notifier()
n.notify("Titolo", "messaggio stile bold", time=5000)


Per far funzionare questo codice è necessario che il pacchetto python-dbus sia installato sul vostro sistema (Ubuntu lo installa per default). Il metodo notify prova ad invocare il metodo self.notif.Notify in due modi diversi a causa di una modifica delle api della libnotify (vedi qui per maggiori info). Il primo metodo infatti funziona su Ubuntu Edgy, il secondo sulla Feisty. Il parametro time server per impostare la durata in millisecondi della notifica.

12 febbraio, 2007

Scaricare i video da YouTube

Oggi mi sono imbattuto in uno script python interessante. Questo script permette di scaricare in modo semplice e voloce i video da youtube senza dover installare nessuna estensione firefox o usare altri programmi impegnativi.
Il programmino può essere scaricato da qui http://www.arrakis.es/~rggi3/youtube-dl/

Per usarlo basta invocarlo da console con per parametro l'url della pagina nella quale è presente il video che vogliamo scaricare :)

Piccolo, semplice, efficace :D

29 gennaio, 2007

Demoni in python

In questo post voglio proporvi un metodo più o meno standard per scrivere processi demoni in python. Un processo demone (per chi non lo sapesse), è un processo che di solito è in esecuzione nel sistema per fornire dei servizi su richiesta all'utente o ad altri processi. Il metodo standard per demonizzare un processo, consiste nell'usare due fork (si legge in giro che due fork garantiscono la correttezza del funzionamento su tutti i sistemi unix). La fork è una chiamata di sistema unix che crea in memoria una copia esatta del processo chiamante e restituisce il pid (process id) del nuovo processo al "genitore" e zero al figlio. Andiamo a vedere il codice:


import os
import sys

class Log:
def __init__(self, logfile):
self.logfile = logfile

def write(self, data):
self.logfile.write(data)
self.logfile.flush()

def daemonize(logfile):
"""
Questa funzione fara' diventare demone il processo che la richiamera'
e redirigera' il suo standard output e standard error sul file logfile
"""
# La maschera' con la quale verranno creati gli eventuali nuovi
# files
UMASK = 0

try:
pid = os.fork()
except OSError, e:
raise Exception, "la prima fork e' fallita: %d (%s)" % \
(e.errno, e.strerror)

if (pid == 0):
os.setsid()
try:
pid = os.fork()
except OSError, e:
raise Exception, "la seconda fork e' fallita: %d (%s)" % \
(e.errno, e.strerror)

if (pid == 0):
os.chdir('/')
os.umask(UMASK)
else:
os._exit(0)
else:
os._exit(0)

sys.stdin.close()
# Redirigo standard output ed error sul log file
try:
sys.stdout = sys.stderr = Log(open(logfile, 'a+'))
except IOError, details:
raise IOError, details

return 0

Un paio di punti da notare:
  • Il secondo figlio imposta come sua directory corrente la root ("/")
  • Lo stdin viene chiuso

Per quanto riguarda il primo punto, ciò viene fatto per prevenire situazioni anomale nelle quali un filesystem non può essere smontato in quanto c'è un processo che lo impedisce. Infatti avrete notato che se apriamo una shell e ci spostiamo con il comando cd in una directory sotto un filesystem montato quest'ultimo non potrà essere smontato finché la nostra shell non "smette" di impegnarlo. Il secondo punto è un po più ovvio: un demone non deve ricevere dati dallo standard input. Probabilmente se dovrà ricevere dati userà metodi differenti, come ad esempio i sockets.

Esempio d'uso


Possiamo salvare il codice appena visto in un modulo python che chiamiamo ad esempio daemon.py per poi richiamarlo in questo modo:

from daemon import daemonize

daemonize("/var/log/mylog.log")

import time
i = 0
while 1:
print i
time.sleep(1)
i = i + 1

Se salviamo questo semplice script in, ad esempio, test.py e lo eseguiamo (da root se vogliamo usare /var/log/mylog.log oppure possiamo cambiare file di log e usare un percorso scrivibile dall'utente non privilegiato, ad esempio la nostra home), quello che otterremo sarà un bel demone in esecuzione in background che ogni secondo andrà a scrivere sul nostro file di log il valore della variabile i. Il demone rimarrà in esecuzione fin quando non lo andremo a uccidere. Per farlo dobbiamo scoprire il suo pid con ps x (l'opzione x è necessaria per vedere i parametri passati ai processi), troviamo qualcosa di simile a 65100 python test.py e lo uccidiamo con kill 65100 (sostituire 65100 con il vostro pid.

20 gennaio, 2007

Esperimento: Una rete che si autoespande

In questo post voglio parlarvi di un esperimento che ho fatto in questi giorni con python. In pratica si tratta di una rete di nodi che si "auto trovano" nella rete mantenendo ognuno una lista con gli ip e i nomi degli altri nodi che viene aggiornata non appena un nodo viene a mancare. La cosa interessante è che non c'è un server centrale a gestire il tutto, ma ogni nodo è uguale all'altro ed ha la stessa importanza.

Come Funziona?
Ogni nodo (che in pratica è un programma python) è composto da 4 trhead ed un loop principale. Un thread (Broadcaster) si occupa di fare il broadcast sulla rete (N.B. quindi funziona solo sulla rete locale) della sua lista di contatti, che contiene l'ip e il nome degli altri nodi. Un secondo thread (Receiver) si occupa di ricevere il traffico broadcast inviato dagli altri nodi e di aggiornare la lista dei contatti. Un terzo thread (TcpListner) rimane in ascolto per connessioni tcp (questo serve agli altri nodi per capire se il nodo è ancora "vivo"). Infine un quarto thread (BlistCleaner) che si occupa di interrogare i nodi nella sua lista per scoprire se sono ancora attivi o meno e di aggiornare di conseguenza la sua lista di contatti.
La prima cosa che un nodo fa è quella di mandare in broadcast il suo ip e il suo nome (passati come parametri). Se ad esempio i nodi sono due, il secondo nodo cattura il broadcast del primo e lo inserisce nella sua lista di broadcast che contiene già se stesso e così via con gli altri nodi. Riporto di seguito il codice dei nodi:

PORT = 50000
TCPPORT = 80000
BCASTIP = '192.168.50.255'

import sys, time
import threading
import socket

class Broadcaster(threading.Thread):
def __init__(self, data):
threading.Thread.__init__(self)
self.data = data
self.exit = False

def run(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('', 0))
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

while 1:
if self.exit: break
data = ""
print "Broadcasting: ", self.data.broad
for item in self.data.broad:
name, ip = item
data += name + ":" + ip + ","
if data != "":
# tolgo l'ultima virgola
data = data[:len(data) - 1]
try:
s.sendto(data, (BCASTIP, PORT))
except socket.error, e:
break
print "Broadcaster error: ", e
time.sleep(2)
s.close()
print "Exit Broadcaster"

class Receiver(threading.Thread):
def __init__(self, data):
threading.Thread.__init__(self)
self.exit = False
self.data = data

def run(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('', PORT))
s.settimeout(0.1)
while 1:
if self.exit: break
try:
data, fromaddr = s.recvfrom(1024)
items = data.split(",")
self.data.rec = []
if len(items) > 0:
for item in items:
name, ip = item.split(":")
self.data.rec.append( (name, ip) )

print "Received: ", self.data.rec
except socket.timeout, e:
pass
except socket.error, e:
break
print "Receiver error: ", e


#print (data, fromaddr)
#print self.list

s.close()
print "Exit Receiver"

class TcpListner(threading.Thread):

def __init__(self, ip):
threading.Thread.__init__(self)
self.myip = ip
self.exit = False

def run(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind( (self.myip, TCPPORT) )
s.listen(1)
s.settimeout(0.1)

while 1:
if self.exit: break
try:
conn, addr = s.accept()
except socket.timeout, e:
pass
except socket.error, e:
s.close()
break
print "TcpListner error: ", e

s.close()
print "Exit TcpListner"

class BlistCleaner(threading.Thread):

def __init__(self, data, myname):
threading.Thread.__init__(self)
self.data = data
self.exit = False
self.myname = myname

def run(self):

while 1:
if len(self.data.broad) == 0: continue
for item in self.data.broad:
name, ip = item
if name != self.myname:
#print "check name: ", name, "ip: ", ip
try:
s = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
s.connect((ip, TCPPORT))
s.close()
except socket.error, e:
s.close()
print "Elimino ",name
self.data.broad.remove( (name, ip) )
self.data.rec.remove( (name, ip) )
print "Lista: ", self.data.broad
time.sleep(1)
if self.exit: break

print "Exit BlistCleaner"

class Data:
pass

data = Data()
data.rec = []
data.broad = []


broad = Broadcaster(data)
broad.setName("Broadcaster")
rec = Receiver(data)
rec.setName("Receiver")
# aggiungo il mio nome
name = sys.argv[1]
myip = sys.argv[2]

data.broad.append( (name, myip) )

tcpl = TcpListner(myip)
tcpl.setName("TcpListner")
cleaner = BlistCleaner(data, name)
cleaner.setName("BlistCleaner")

tcpl.start()
cleaner.start()

broad.start()
rec.start()

t = time.time()
while 1:
try:
if len(data.rec) > 0:
for n in data.rec:
if n not in data.broad:
data.broad.append(n)
time.sleep(0.1)
now = time.time()
if now - t > 3:
#print "rlist: ", rec.list
#print "blist: ", broad.list
t = now
except KeyboardInterrupt:
broad.exit = True
rec.exit = True
tcpl.exit = True
cleaner.exit = True
sys.exit(0)

Naturalmente sono da impostare l'indirizzo di broadcast e se salviamo il file come node.py dobbiamo eseguirlo come:

python [nomenodo] [mio_ip]
Per ora è solo un esperimento ma magari in futuro potrebbe risultarmi utile per qualcosa o magari può servire a voi come spunto per una nuova idea!

09 gennaio, 2007

Python: Udp broadcasting

Oggi voglio riportarvi un esempio di come funziona e come si implementa il broadcast dei pacchetti in python. Prima di iniziare, ricordo a chi non lo sapesse che oltre ai socket TCP (SOCK_STREAM) i quali garantiscono (usando il protocollo tcp) che i dati che vengono trasmessi arrivano correttamente a destinazione, esistono i socket udp (SOCK_DGRAM) i quali non danno nessuna garanzia (esistono anche altri tipi di socket di cui non ci preoccuperemo in questo post). I socket tcp non permettono il broadcast in quanto sono socket basati sulla connessione. Quelli udp invece consentono il broadcast perché sono connection-less. Vi riporto ora un esempio davvero semplice di un broadcast server e di un client che riceve informazioni dal server.

#server

PORT = 50000

import sys, time
from socket import *

s = socket(AF_INET, SOCK_DGRAM)
s.bind(('', 0))
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)

while 1:
try:
data = repr(time.time())
# non funziona se non c'e' un default
# routing

#s.sendto(data, ('<broadcast>', PORT))

# se invece specifico un indirizzo di
# broadcast funziona
s.sendto(data, ('192.168.0.255', PORT))

time.sleep(2)
except KeyboardInterrupt:
sys.exit(0)


Come potete vedere il codice del server è molto banale. In pratica non fa altro che mandare in broadcast ogni 2 secondi il tempo misurato dal sistema. Notiamo che stiamo usando i datagram sockets e li abbiamo messi in modalità broadcast con
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)

A questo punto notate il commento sull'istruzione che usa <> come indirizzo al quale inviare i dati. Sul manuale di python questo non è specificato ma è così. Se vogliamo che i dati vengano ricevuti dal client dobbiamo specificare l'indirizzo di broadcast corretto.
Vediamo ora il codice del client:

import sys
from socket import *

PORT = 50000

s = socket(AF_INET, SOCK_DGRAM)
s.bind(('', PORT))
while 1:
try:
data, fromaddr = s.recvfrom(1024)
print (data, fromaddr)
except KeyboardInterrupt:
sys.exit(0)
Come possiamo notare il client non conosce a priori l'indirizzo del server ma riceve semplicemente i dati. Ricordiamoci comunque che il protocollo udp non garantisce nulla sulla consegna degli stessi. Quindi è possibile che ciò che invia il server non arrivi correttamente a destinazione

08 gennaio, 2007

Pygtk: Un widget in una TextView

Se vi è capitato di imbattervi nel problema di dover inserire una immagine o qualsiasi altro widget in una TextView e non sapete come fare, di seguito vi proporrò una soluzione. Il principio è semplice:

  • troviamo il punto in cui vogliamo inserire il widget e creiamo un TextIter
  • creiamo un' ancora al punto in cui si trova l'iter
  • ancoriamo il widget
Nell'esempio che riporterò, per semplicità considererò come iter la fine del buffer associato alla TextView.

import gtk

tv = gtk.TextView()
buf = tv.get_buffer()
iter = buf.get_end_iter()
anchor = buf.create_child_anchor(iter)

img = gtk.Image()
# sostituire con un path ad un file immagine
# esistente
img.set_from_file("path_del_file")
img.show()

tv.add_child_at_anchor(img, anchor)

tv.show()
sw = gtk.ScrolledWindow()
sw.add(tv)
sw.show()
frame = gtk.Frame()
frame.show()
frame.add(sw)
w = gtk.Window()
w.add(frame)
w.set_default_size(400, 300)
w.show_all()
w.connect("destroy", lambda w: gtk.main_quit())
gtk.main()

La cosa interessante è che in questo caso abbiamo usato una immagine, ma avremmo potuto "caricare" nella text view ad esempio un pulsante o un altro widget

04 gennaio, 2007

Una text view più funzionale

Stavo cercando in rete una text view per il progetto Planimo che fosse un po più funzionale di quella fornita di default con le pygtk. In particolare dovrebbe supportare in parte l'html e magari la possibilità di inserire immagini in modo semplice. Dovrebbe essere possibile ad esempio passare da uno stile per il testo all'altro utilizzando i normali tag html, senza dover scendere a "basso" livello per occuparsi di iteratori e TextTag. Purtroppo ho trovato poco e quindi mi sa che mi tocca scrivermene una da me. Non dovrebbe essere troppo complesso. Cercherò di scriverla in modo che possa essere facilmente riutilizzabile e se otterrò un risultato decente la pubblicherò su questo blog!

Comunque credo che un widget di questo tipo potrebbe far comodo a molti e potrebbe essere il caso che gli sviluppatori delle gtk ne pensassero uno per il kit

11 dicembre, 2006

Un modulo portabile per suoni in python

Il modulo che vi riporterò di seguito serve per poter far emettere un suono al vostro programma python (in genere suoni brevi associati a particolari eventi) senza preoccuparvi se vi trovate su piattaforma windows o su linux chiamando una semplice funzione con parametro il nome del file da suonare.
Ecco un basilare esempio di utilizzo:



import sndplay

sndplay.play("percorso_del_file.wav")

Come avrete notato è molto semplice da usare. Se vi trovate su una piattaforma diversa dalle due supportate la funzione non fa nulla. Come parametro ho inserito un file wav. In realtà su linux viene accettato come parametro un qualsiasi formato supportato dalle librerie gstreamer ma se mettiamo un file diverso dal wav perdiamo la portabilità perché (almeno per ora) su windows è supportato solo il formato wav.

Passiamo ora al codice:


import threading
import os

(
GSTPLAY,
WINPLAY,
NOENGINE
) = range(3)

try:
import gst
import gobject
ENGINE = GSTPLAY
except ImportError:
try:
import winsound
ENGINE = WINPLAY
except ImportError:
ENGINE = NOENGINE

class __GstPlayThread(threading.Thread):
def __init__(self, ply):
self.ply = ply
threading.Thread.__init__(self)
def run(self):
self.ply.set_state(gst.STATE_PLAYING)
def bus_event(bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
self.ply.set_state(gst.STATE_NULL)
return True
self.ply.get_bus().add_watch(bus_event)


def __gstplay(filename):
cwd = os.getcwd()
location = os.path.join(cwd, filename)
ply = gst.element_factory_make("playbin", "player")
ply.set_property("uri", "file://" + location)
pt = __GstPlayThread(ply)
pt.start()

def __winplay(filename):
cwd = os.getcwd()
location = os.path.join(cwd, filename)
import thread
def wplay():
winsound.PlaySound(location,
winsound.SND_FILENAME)
thread.start_new_thread(wplay, ())

if ENGINE == GSTPLAY:
play = __gstplay
pass
elif ENGINE == WINPLAY:
play = __winplay
else:
def play(filename):
pass


Attenzione: su linux è supportata solo la versione 0.10 delle pygst!!!

Come si può notare l'effettivo playing del file viene eseguito da un thread in modo che la play non sia bloccante. Cio vuol dire che mentre il suono viene riprodotto in background, il nostro codice può continuare ad essere eseguito ;)

Questo modulo l'ho scritto inizialmente per planimo ma ho pensato che sarebbe stato utile postarlo a parte con qualche spiegazione a contorno


28 novembre, 2006

Threads e gui pygtk: Soluzione definitiva

Se avete letto questo post vi avevo spiegato come usare
gtk.gdk.threads_init(), gtk.gdk.threads_enter() e
gtk.gdk.threads_leave().
Nel post successivo vi ho spiegato che quell'approccio però
penalizza la portabilità su windows. Ora per rendere il nostro codice
portabile potremmo utilizzare due diverse soluzioni. La prima che vi
propongo la mostro solo per completezza ma la sconsiglio e poi vi
spiegherò perché. La seconda è invece la più sicura ed è garanzia
di funzionamento anche in situazioni con più threads attivi.
Allora la prima soluzione consiste nell'eseguire le funzioni che danno
problemi con una gobject.idle_add() e "circondare" il main
con gtk.gdk.threads_enter() e gtk.gdk.threads_leave()
Il codice diventa (date uno sguardo qui per capire le differenze):


import threading
import time
import gtk
import gobject


gtk.gdk.threads_init()



class MyThread(threading.Thread):
def __init__(self, pbar):
self.pbar = pbar
self.stop = False
threading.Thread.__init__(self)

def run(self):
t = 0
while not self.stop :
gtk.gdk.threads_enter()
# uso la idle_add
gobject.idle_add(self.pbar.set_fraction, t)
gtk.gdk.threads_leave()

t = t + 0.1
if t > 1.0:
t = 0.0

time.sleep(0.5)


def exit(arg):
th.stop = True
gtk.main_quit()

window = gtk.Window()
pbar = gtk.ProgressBar()
window.add(pbar)
window.show_all()

window.connect('destroy', exit)


th = MyThread(pbar)
th.start()

gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()

Questo codice funziona anche su windows ma se i threads iniziano a diventare tanti ( ma non solo ) spesso si hanno dei problemi. Quindi se non volete impazzire vi consiglio vivamente questa seconda soluzione che consiste nell'evitare come la morte le varie threads_enter, threads_leave e init_threads e fare in modo che tutte le operazioni che hanno a che fare con le gtk vengano eseguite dallo stesso thread. L'esempio precedente può essere riscritto in questo modo:

import threading
import time
import gtk


class MyThread(threading.Thread):
def __init__(self, main):
self.main = main
threading.Thread.__init__(self)

def run(self):
while not self.main.stop :
self.main.pbar_fraction += 0.1
if self.main.pbar_fraction > 1.0:
self.main.pbar_fraction = 0.0

time.sleep(0.5)

class Main:
def __init__(self):
self.pbar_fraction = 0
self.stop = False

window = gtk.Window()
self.pbar = gtk.ProgressBar()
window.add(self.pbar)
window.show_all()
window.connect('destroy', self.exit)

th = MyThread(self)
th.start()

self.main_loop()

def exit(self, arg):
self.stop = True

def main_loop(self):
while not self.stop:
self.pbar.set_fraction(self.pbar_fraction)
while gtk.events_pending():
gtk.main_iteration()
time.sleep(0.01)

Main()

Come vedete non abbiamo usato neppure il gtk.main() e
il gtk.main_quit() ma facciamo tutto da noi ;)
Questo codice è portabile, funzionante e non vi farà imprecare :)

27 novembre, 2006

Problema con gtk.gdk.threads_init()

Purtroppo la descrizione dell'utilizzo di questa funzione che ho fatto precedentemente sul blog sembra non funzionare sulla versione windows di python e delle pygtk.Il problema è che non appena si incontra la funzione gtk.gdk.threads_init(), il programma su windows si blocca e non c'è verso di farlo continuare. Purtroppo così i programmi scritti in pygtk perdono la loro caratteristica di portabilità.

Aggiornamento: qui propongo una soluzione

23 novembre, 2006

Threads e gui pygtk in python

Se vi è capitato di programmare con le gtk in python (e non solo) utilizzando i threads, vi sarete accorti che spesso vengono fuori problemi.

Perchè quel maledetto thread non riesce ad aggiornare la gui?

Facciamo un semplice esempio per capirci meglio.


import threading
import time
import gtk

class MyThread(threading.Thread):
def __init__(self, pbar):
self.pbar = pbar
self.stop = False
threading.Thread.__init__(self)

def run(self):
t = 0
while not self.stop :
self.pbar.set_fraction(t)

t = t + 0.1
if t > 1.0:
t = 0.0

time.sleep(0.5)


def exit(arg):
th.stop = True
gtk.main_quit()

window = gtk.Window()
pbar = gtk.ProgressBar()
window.add(pbar)
window.show_all()

window.connect('destroy', exit)

th = MyThread(pbar)
th.start()

gtk.main()


Questo semplice programma non fa altro che creare una finestra gtk, aggiungergli un widget progressbar e creare un thread che aggiorna il progresso ogni mezzo secondo. Beh se provate ad eseguirlo, la finestra con la progress bar verrà creata ma non ci sarà alcun aggiornamento.

Il problema sta nel fatto che il thread, prova ad operare sull'oggetto gtk. Per fare ciò le librerie gtk prevedono una procedura particolare.
Prima di tutto va richiamata la funzione gtk.gdk.threads_init() prima della chiamata a gtk.main(). Poi ogni volta che si vuole accedere ad un oggetto gtk vanno usate le due funzioni:
gtk.gdk.threads_enter()
gtk.gdk.threads_leave()
Il codice corretto quindi è:

import threading
import time
import gtk

# chiamo threads_init()
gtk.gdk.threads_init()

class MyThread(threading.Thread):
def __init__(self, pbar):
self.pbar = pbar
self.stop = False
threading.Thread.__init__(self)

def run(self):
t = 0
while not self.stop:
# threads_enter
gtk.gdk.threads_enter()
self.pbar.set_fraction(t)
# threads_leave
gtk.gdk.threads_leave()

t = t + 0.1
if t > 1.0:
t = 0.0

time.sleep(0.5)


def exit(arg):
th.stop = True
gtk.main_quit()

window = gtk.Window()
pbar = gtk.ProgressBar()
window.add(pbar)
window.show_all()

window.connect('destroy', exit)

th = MyThread(pbar)
th.start()

gtk.main()


Se lo eseguite vedrete la nostra progress bar finalmente in funzione!

21 novembre, 2006

Invio file su socket in python

In questo post spiegherò come è possibile inviare un file attraverso
la rete utilizzando i socket python. Quello che andremo a costruire
è un piccolo server che rimarrà in ascolto per ricevere un file che
verrà inviato da un client il quale lo preleverà dal disco rigido e lo
invierà via socket.
Ecco il codice del serverino che chiamiamo receive.py


#! /usr/bin/env/python

import socket
import sys

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 9900))
sock.listen(1)

filename = sys.argv[1]

s, addr = sock.accept()
file = open(filename, 'wb')
while 1:
data = s.recv(1024)
if not data: break
file.write(data)
file.close()
s.close()

In pratica non fa altro che mettersi in ascolto sulla porta 9900 ed
aspettare la connessione da parte del client. Non appena il client
si connette, crea un file con il nome che gli è stato passato come
parametro e ci scrive dentro i dati inviatigli.

Vediamo ora il codice del client che chiamiamo send.py:

#! /usr/bin/env/python

import sys
import socket

filename = sys.argv[1]
host = sys.argv[2]

file = open(filename, 'rb')
sock = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
sock.connect((host, 9900))

while 1:
data = file.read(1024)
if not data: break
sock.sendall(data)
file.close()
sock.close()


Il client apre il file passatogli come primo parametro e lo invia al
server (secondo parametro).
Come si può vedere si usa una normale sendall.
La documentazione python a riguardo potrebbe essere fuorviante.
Infatti viene indicato come sendall(string[, flags])
Cioè il primo parametro viene indicato come string mentre noi
vogliamo inviare un file binario generico. In realtà il metodo
funziona comunque perché la stringa in questo caso è intesa
un po come un array di char ovvero un array di bytes ;)