Corso Python Magneti Marelli
===================================
:Tenuto: 19-23 Settembre 2005
:Autore: Michele Simionato
:E-mail: michele.simionato@gmail.com
*Queste dispense sono un compendio informale e molto sintetico di quanto
svolto durante il corso. Esse non si pongono in nessun modo come un testo
sistematico di programmazione in Python. Il loro scopo principale è quello
di tenere traccia, per quanto possibile, di quanto si è detto. Lo
scopo secondario è quello di invogliare i partecipanti e tutti
gli eventuali lettori ad affrontare gli argomenti qui trattati per forza
di cose in maniera abbozzata, in maniera più sistematica andando a
consultare le fonti più adatte alla bisogna, siano essi libri di testo,
la documentazione ufficiale, newsgroups, siti Web dedicati e, in ultima
instanza, il codice sorgente stesso, l'unico riferimento definitivo.*
.. contents::
I messaggi fondamentali del corso
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Studiare paga*
---------------
Grazie alla sua macchina del tempo, Guido ha risolto i problemi
che vi stanno affliggendo ora dieci anni fa, e sono già nel linguaggio
e nella libreria standard. Per esempìo avete scoperto di avere a
disposizione:
- le stringhe di documentazione per darvi la documentazione automatica
del codice tramite strumenti quali pydoc ed altri;
- il try ... finally per garantirvi un "gracefull exit" del vostro
programma, anche in presenza di eccezioni inaspettate;
- i metodi setUp e tearDown di unittest.TestCase, che vi permettono
di inizializzare e di pulire propriamente l'ambiente per ogni test;
- sia unittest che doctest vi permetto di gestire le eccezioni (nel
senso di gestire le eccezioni "buone", quelle che vi aspettate);
- call e Popen in subprocess per aprire processi, kill per ucciderli
(usando win32api.TerminateProcess);
- twisted vi gestisce la comunicazione tra processi e tutte le eccezioni
senza problemi
Una settimana di studio oggi può risparmiarvi mesi di frustrazioni
domani. Sfruttare le risorse disponibili, quali tutorial, howto,
libri, documentazione, siti Web (come il Python cookbook) e soprattutto
i newsgroups. Ma prima di postare su di un newsgroup leggetevi "How to
ask smart questions" e ricordatevi sempre che "Google is you friend".
*Disaccoppiamento* e *KISS*
----------------------------
Se potere, scomponete l'applicazione in componenti separati e testateli
separatamente. Scegliete architetture che vi permettono di disaccoppiare
i problemi. Se potere fare le cose semplici, fatele semplici.
Abbiate un core minimale che faccia poco, ma che siate sicuri che
funzioni. Non complicatevi la vita.
*Non nascondete gli errori sotto il tappeto*
--------------------------------------------
Non trappate l'inaspettato, è una ricetta sicura per avere rogne.
Usate lo *sviluppo incrementale*, cioè verificate il funzionamento del
programma SUBITO, e fissate l'errore SUBITO. Non debuggate mai
a posteriori, se potete evitarlo, ma scrivete codice testato fin da subito.
Abbiate una bella suite di test automatici di installazione, per accorgervi
da subito se c'è un problema quando installate il software su di un'altra
macchina.
Modulo 1: Strumenti di introspezione, sviluppo e debugging
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*In questo modulo discuterò gli strumenti da me utilizzati per sviluppare
in Python sotto Windows. Parlerò di Cygwin come ambiente di lavoro,
di Python e di IPython come interpreti interattivi, di Idle e di PythonWin
come IDE, di pydoc e minidoc come tools di introspezione. Inoltre discuterò
alcune utili librerie e frameworks per Python (Numeric, matplotlib, gnuplot,
etc.).*
Strumenti di introspezione e come ottenere aiuto
-------------------------------------------------
+ **Help in linea:**
Ottimo
+ **Pydoc:**
Standard, può essere usato dalla riga di comando.
``pydoc -g`` oppure ``python -mpydoc -g``
vi dà la versione grafica, con funzionalità di search.
+ **Help di ActiveState:**
Eccezionale, contiene anche libri di testo, FAQs e how-tos.
+ **Google:**
Di tutto, di più.
+ **Newsgroups:**
Risolvono i vostri problemi per voi, e gratis.
Ambienti di sviluppo
--------------------
+ **Cygwin:**
Emulatore Unix sotto Windows. Vi permette di lavorare dalla riga di
comando comodamente. Evitate di perdere tempo a cliccare a destra e
a manca e avete una stabilità maggiore di quella di un ambiente
grafico.
+ **Idle:**
Ambiente di sviluppo per Python che viene con la distribuzione standard.
Un pò povero, con qualche difetto, ma semplice e portabile ovunque.
+ **PythonWin:**
Ambiente di sviluppo per Python che viene con la distribuzione ActiveState
per Windows. Più sofisticato di Idle e più integrato con Windows. Ha dei
pro e dei contro.
+ **WingIDE, Eric/Qt Designer, Komodo, Boa Constructor:**
Ambienti di sviluppo di cui esiste una versione commerciale. In generale
hanno un look più professionale e sono utili se uno deve dare interfacce
grafiche. WingIDE ha il debugger migliore, a quanto ho sentito.
+ **Emacs o Vi:**
Un Real Programmer (TM) vi dirà che al mondo esistono due soli editor:
Emacs e Vi. Tutto il resto è spazzatura. Emacs e Vi girano bene sotto
Windows, ma funzionano al meglio sotto Unix.
Strumenti di debugging
------------------------------------------------
- **print:** è la soluzione usata dai migliori programmatori Python;
- **pdb:** sembra il debugger dei poveri, ma è standard e funziona;
- **mille altri debuggers**, compreso un nuovissimo winpdb dall'autore
del pdb, da valutare;
- **programmazione test-driven:** con questa metodologia la stragrande
maggioranza dei bugs vengono individuati subito, non appena il
codice viene scritto, e vi troverete ad usare il debuggere dieci
volte di meno di quanto fate ora.
Strumenti utili per l'utenza scientifica/ingegneristica
------------------------------------------------------------------
- **Numeric e/o Numarray:**
Tutto quello che serve per far conti con le matrici. Numarray è il
successore di Numeric, con compatibilità al 99%.
- **matplotlib:**
Il meglio per plottare grafici 2D. Richiede Numeric/Numarray.
- **ipython:**
Il miglior interprete interattivo per Python. Eccezionali capacità
di introspezione e di debugging (è integrato con il Python debugger
pdb della libreria standard).
Modulo 2: Programmazione di base in Python
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*In questo modulo si ripasseranno molto brevemente le basi di Python e
si discuteranno le soluzioni al questionario di ammissione. Lo scopo
più che altro è quello di conoscersi e di chiarire il livello medio
dei partecipanti e coprire eventuali buchi nella preparazione di base.*
Le soluzioni agli esercizi sono riportate nell'ultimo capitolo. Durante le
correzioni mi sono accordi di vari buchi nella programmazione
di base in Python che si è cercato di riempire.
Encoding
----------------------------------
[Illustrato al corso 1] In versioni recenti di Python (dalla 2.3)
se il vostro script contiene dei caratteri accentati (anche nei commenti)
ottenete un warning tipo questo::
sys:1: DeprecationWarning: Non-ASCII character '\xe0' in file x.py
on line 1, but no encoding declared; see
http://www.python.org/peps/pep-0263.html for details
La soluzione e' aggiungere in testa al vostro programma una dichiarazione
tipo questa::
#-*- encoding: latin-1 -*-
(usate latin-15 se il vostro programma contiene il simbolo dell'Euro).
Pathnames
-----------------------------
Sfortunatamente Windows usa la backslash come separatore dei pathnames;
la backslash e' anche il carattere di escaping, quindi ci sono
problemi:
>>> print 'documenti\nonna' # \n interpretato come newline
documenti
onna
La soluzione e' usare raw strings:
>>> print r'documenti\nonna'
documenti\nonna
Alternativamente, in versioni recenti di Windows, avreste potuto usare
anche "/" come separatore:
>>> import os
>>> os.path.exists(r'C:\Python24\python.exe')
True
>>> os.path.exists(r'C:/Python24/python.exe')
True
Insiemi
-----------------------------------------
In Python 2.3 gli insiemi sono stati aggiunti come un nuovo tipo di dati
nel modulo sets (by Alex Martelli); in Python 2.4 gli insiemi sono diventati
un tipo builtin.
>>> s = set('pippo')
>>> s
set(['i', 'p', 'o'])
>>> 'i' in s
True
>>> 'p' in s
True
>>> 'o' in s
True
>>> 'z' in s
False
E' possibile calcolare l'unione e l'intersezione di insiemi:
>>> t = set('aglio')
>>> s | t
set(['a', 'g', 'i', 'l', 'o', 'p'])
>>> s & t
set(['i', 'o'])
Il modo compatibile con il passato per usare i sets nello stesso modo nella
2.3 e nella 2.4 è il seguente::
try:
set:
except NameError:
from sets import Set as set
Guardare la documentazione standard per saperne di più.
Differenza tra mutabili e immutabili
---------------------------------------
Questa è una causa comune di confusione in Python:
>>> a = 1 # i numeri sono immutabili
>>> b = a
>>> a += 1
>>> a
2
>>> b
1
>>> a = [1] # le liste sono mutabili
>>> b = a
>>> a += [1]
>>> a
[1, 1]
>>> b
[1, 1]
getattr e setattr
-------------------------------------------
[Svolto al corso 1] ``getattr`` e ``setattr`` servono per richiamare e
settare metodi il cui nome viene determinato dinamicamente::
#
class C(object):
def m1(self):
print "chiamato m1"
def m2(self):
print "chiamato m2"
if __name__ == "__main__":
c = C()
method = raw_input("Che metodo devo chiamare? [m1 o m2] ")
getattr(c, method)()
#
*Non usate exec quando getattr basterebbe!*
>>> method = 'm1'
>>> exec 'c.%s()' % method # funziona ma è brutto
chiamato m1
>>> getattr(c, method)() # il modo giusto
chiamato m1
Per ``setattr`` vedere la documentazione.
Gestione dei processi
-----------------------
Come far partire un processo in parallelo::
import subprocess
PLAYER ="mplay32"
def play_song(song):
subprocess.Popen([PLAYER, "/play", "/close", song]) # NON BLOCCA!
print "Partito"
if __name__ == "__main__":
play_song("c:/Documents and Settings/micheles/Desktop/Music/1. "
"Theme from Harry's Game.mp3")
``subprocess.call`` fa partire il processo e blocca il programma fintanto
che il processo non è terminato. Ho anche fatto vedere cosa succede
se uno dei processi solleva qualche eccezione inaspettata ma viene chiuso
correttamente grazie al try .. finally::
#
"Chiama due processi proc1a.py e proc1b.py"
import subprocess
CMD_a = ["python", "-c", "import proc1a; proc1a.main()"]
CMD_b = ["python", "-c", "import proc1b; proc1b.main()"]
if __name__ == "__main__":
p_a = subprocess.Popen(CMD_a)
p_b = subprocess.Popen(CMD_b)
#
Processo 1a::
#
import time
def main():
for i in range(10):
print "hello"
time.sleep(1)
if __name__ == "__main__":
main()
#
Processo 1b::
#
#-*- encoding: latin-1 -*-
import time, sys
def main():
try:
f = file("proc1b.py")
for i in range(10):
print "world"
if i == 5:
raise RuntimeError("Ahia!")
time.sleep(1)
finally:
f.close()
print "Il file è stato chiuso correttamente."
if __name__ == "__main__":
main()
#
Ho anche illustrato brevemente come si possono gestire i processi da
Twisted::
#
"Un processo che genera numeri casuali e li salva nel file data.txt"
import random
def main():
ro = random.Random()
out = file("data.txt", "w")
for number in ro.sample(range(1000), 100):
print >> out, number
out.close()
print "Dati salvati sul file 'data.txt'"
if __name__ == "__main__":
main()
#
#
"Un processo che genera l'istogramma histo.png dai dati in data.txt"
from pylab import hist, savefig
def main():
hist([int(n) for n in file("dat.txt")], 10)
savefig("histo.png")
print "Istogramma salvato sul file 'histo.png'"
if __name__ == "__main__":
main()
#
#
"Il main che chiama proc2a.py e proc2b.py nell'ordine e gestisce gli errori"
import webbrowser, sys
if sys.platform == "win32":
from twisted.internet import win32eventreactor
win32eventreactor.install()
from twisted.internet.utils import getProcessOutput
from twisted.internet import reactor
def scrivi_messaggio(err):
print err.getErrorMessage()
reactor.stop()
import pdb; pdb.set_trace() # fa partire il debugger in caso di errore
def visualizza_histo(out_di_genera_histo):
print out_di_genera_histo
webbrowser.open("histo.png")
def genera_histo(out_di_genera_dati):
print out_di_genera_dati
getProcessOutput(sys.executable, (r"c:\corso\processi\proc2b.py",)) \
.addCallback(visualizza_histo) \
.addErrback(scrivi_messaggio)
def genera_dati():
getProcessOutput(sys.executable, (r"c:\corso\processi\proc2a.py",)) \
.addCallback(genera_histo) \
.addErrback(scrivi_messaggio)
if __name__ == "__main__":
reactor.callLater(0, genera_dati) # call "genera_dati" after 0 seconds
reactor.run()
#
In questo esempio ho usato ``sys.executable``, che contiene il nome
completo dell'eseguibile Python (per esempio ``C:\Python24\python.exe``)
con cui il programma principale è stato lanciato. Questo assicura che
i processi secondari vengano lanciati con quella versione di Python
(utile se avete installato contemporaneamente piu' versioni di Python
e ci possono essere dei dubbi, oppure se il path non e' settato
correttamente e l'eseguibile Python non viene trovato).
A volte, nonostante tutta la buona volontà, i processi vanno fuori
controllo. E' possibile ammazzarli brutalmente, con una funzione
``kill`` come la seguente::
import os
try: # we are on Windows
import win32api
def kill(pid, *unix_compatibility_dummy_args):
handle = win32api.OpenProcess(1, False, pid) # open handle to kill
win32api.TerminateProcess(handle, -1)
win32api.CloseHandle(handle)
os.kill = kill # fix os
except ImportError: # we are on Unix
pass
In questo modo di fare, il modulo 'os' della libreria standard viene
fissato automaticamente, aggiungendogli una funzione 'kill' che è
mancante nell'ambiente Windows ma che può facilmente essere implementata
usando le win32api (che non vengono con la distribuzione standard ma sono
incluse con la distribuzione dell'ActiveState).
Naturalmente cambiare moduli della libreria standard al volo NON È
CONSIGLIATO, ma è sempre meglio che modificare a mano il codice
sorgente e mantenere una propria versione modificata.
Iteratori e generatori
----------------------------
Un iterabile è un qualunque oggetto su cui si può iterare con un
ciclo "for"; un iteratore è un oggetto con un metodo .next().
Il modo più comune per definire iteratori è tramite un generatore,
cioè una "funzione" con uno "yield":
>>> def gen123():
... yield 1
... yield 2
... yield 3
...
>>> it = gen123()
>>> it.next()
1
>>> it.next()
2
>>> it.next()
3
>>> it.next()
Traceback (most recent call last):
File '', line 1, in ?
StopIteration
Un ciclo "for" internamente converte l'iterabile in un iteratore,
chiama il metodo ".next()" successivamente e trappa l'eccezione StopIteration,
uscendo dal loop quando non c'è più nulla su cui iterare:
>>> it = gen123()
>>> for i in it: print i
...
1
2
3
Modulo 3. Tipici errori di programmazione e gestione delle eccezioni
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Si discuteranno buoni e cattivi esempi di programmazione presi da software
reale scritto alla Magneti Marelli. Si discuteranno alcune tecniche
per interpretare i tracebacks di Python e per identificare l'origine dei
problemi.*
L'errore più tipico con le eccezioni
--------------------------------------
Bisogna assolutamente evitare codice come il seguente::
try:
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
bla-bla
except:
bla-bla
Nel blocco ``try`` dev'esserci la cosa più semplice possibile, per
limitare i tipi di eccezione che possono nascere. Inoltre l'except nudo
e' orribile perchè trappa qualunque cosa, anche quello che non vorreste.
La cosa giusta da fare è del tipo::
try:
bla-bla
except MyException, e: # sempre specificare l'eccezione aspettata
print e
except OtherException,e:
print e
...
Non trappate l'inaspettato, altrimenti non capirete mai qual è stata
l'origine di un problema.
Il try .. finally è una grande idea
----------------------------------------------------------------
Molto spesso quello che volete non è tanto il try .. except, quanto il
try .. finally: il vantaggio del try .. finally è che
*non vi nasconde l'eccezione* e nello stesso tempo vi *garantisce che quello
che deve essere chiuso correttamente venga chiuso correttamente* in ogni caso.
C'è un eccezione a questa regola: se ammazzate un processo di brutto con un
kill, il try .. finally non può salvarvi. Il try .. finally vi
salva per tutte le eccezioni Python, compreso il CTRL-C (KeyboardInterrupt)
e il sys.exit() (SystemExit).
>>> try:
... raise RuntimeError("Ahia")
... finally:
... print "Io vengo eseguito SEMPRE, anche se c'è un'eccezione!"
...
Io vengo eseguito SEMPRE, anche se c'e' un'eccezione!
Traceback (most recent call last):
File '', line 2, in ?
RuntimeError: Ahia
Uso di assert
--------------
Per asserire che una condizione è verificata con certezza:
>>> def div2(x):
... assert isinstance(x, int), '%s non è un numero intero' % x
... return x/2
...
>>> div2(14)
7
>>> div2(14.0)
Traceback (most recent call last):
File '', line 1, in ?
File '', line 2, in div2
AssertionError: 14.0 non è un numero intero
Tipicamente si usa in "sanity checks", per essere sicuri che un parametro
sia esattamente quello che ci si aspetta, in casi di eccezioni gravi; se
l'assert non è rispettato, tutto il programma deve bloccarsi.
Non usate exec
----------------------------
'exec' è un costrutto pericolosissimo che va riservato solo a chi sa
cosa sta facendo. Spesso e volentieri si usa soltanto per ignoranza
dell'esistenza di una soluzione migliore.
Esempio::
exec file("myscript.py").read()
è UN ERRORE GRAVE per vari motivi.
In primo luogo, in caso di eccezioni sollevate in 'myscript.py'
perdete informazione su dove si trova l'errore (cioè nel file "myscript.py")
e rendete impossibile la vita al debugger; in secondo luogo, sporcate il
namespace del vostro programma in maniera potenzialmente pericolosa. La cosa
giusta da fare è::
dic = {}
execfile("myscript.py", dic)
che non perde informazioni e non sporca il namespace, perchè i nomi
definiti in myscript.py verranno confinati nel dizionario. Leggetevi
la documentazione di 'execfilè per saperne di più.
Come far partire pdb automaticamente in caso di eccezioni inaspettate
-----------------------------------------------------------------------
[Illustrato al corso 1]
::
#
# recipe in the Python cookbook first edition, chapter 14.5
import sys
def info(type, value, tb):
if hasattr(sys, 'ps1') or not sys.stderr.isatty() or \
type == SyntaxError:
# we are in interactive mode or we don't have a tty-like
# device, so we call the default hook
sys.__excepthook__(type, value, tb)
else:
import traceback, pdb
# we are NOT in interactive mode, print the exception...
traceback.print_exception(type, value, tb)
print
# ...then start the debugger in post-mortem mode.
pdb.pm()
sys.excepthook = info
#
Se un programma importa ``exc_debug``, il Python debugger partirà
automaticamente in caso di eccezioni non trappate. Per esempio eseguire
::
#
import exc_debug
a = 1
b = 0
a/b
#
fa partire il debugger::
$ python example.py
Traceback (most recent call last):
File "example.py", line 4, in ?
a/b
ZeroDivisionError: integer division or modulo by zero
> /mnt/hda2/cygwin/home/micheles/md/pypers/marelli/materiale/example.py(4)?()
-> a/b
(Pdb) print a
1
(Pdb) print b
0
Pdb) !b = 1 # cambia il valore di b
(Pdb) print a/b
1
Date ``help pdb`` dall'interno del debugger per avere informazioni sul suo
funzionamento.
Eccezioni e threads
-------------------------
Un'eccezione non trappata blocca soltanto il thread in cui si è verificata,
NON tutto il programma::
#
import threading, time, sys
def print_hello():
for i in range(10):
print "hello"
if i == 5:
raise RuntimeError("Problema a runtime")
time.sleep(1)
def print_world():
for i in range(10):
print "world"
time.sleep(1)
threading.Thread(target=print_hello).start()
threading.Thread(target=print_world).start()
#
dà come output::
$ python esempio1.py
hello
world
hello
world
hello
world
hello
world
hello
world
hello
world
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python2.4/threading.py", line 442, in __bootstrap
self.run()
File "/usr/lib/python2.4/threading.py", line 422, in run
self.__target(*self.__args, **self.__kwargs)
File "esempio1.py", line 7, in print_hello
raise RuntimeError("Problema a runtime")
RuntimeError: Problema a runtime
world
world
world
world
Quindi uno è costretto a implementare un meccanismo di controllo, tipo
il seguente::
import threading, time, sys
END = False
def print_hello():
global END
i = 0
while not END:
i += 1
print "hello"
if i == 5:
try:
raise RuntimeError("Problema a runtime")
except RuntimeError, e:
END = True
time.sleep(1)
def print_world():
i = 0
while not END:
print "world"
time.sleep(1)
threading.Thread(target=print_hello).start()
threading.Thread(target=print_world).start()
Questa è una soluzione artigianale, che usa una variabile globale, sfruttando
il fatto che le variabili globali sono condivise fra tutti i thread (questo
può anche causare danni, se non si sta attenti).
La strada consigliata per comunicare fra threads è quella di usare una coda
[esempio svolto al corso 1]::
"Esempio di due threads comunicanti tramite una queue."
import time, threading, Tkinter, Queue
queue = Queue.Queue()
def print_hello():
for i in range(10):
try:
messaggio = queue.get_nowait()
except Queue.Empty:
pass
else:
if messaggio == "terminate": break
print "%s, hello" % i
time.sleep(1)
def print_world():
for i in range(10):
print "%s, world" % i
if i == 5:
queue.put("terminate") # manda il messaggio di terminazione
root.quit()
raise RuntimeError("Errore nel thread print_world!")
time.sleep(1)
root = Tkinter.Tk()
for func in print_hello, print_world:
th = threading.Thread(target=func)
th.start()
root.mainloop()
Questo esempio fa anche vedere che chiudere la finestrella grafica NON
uccide i threads che stanno girando.
I meccanismi di controllo per bloccare i thread sono notoriamente fragili
e piccoli errori possono farvi grossi danni.
Debuggare i threads è notoriamente un macello.
L'unica soluzione vera è evitare i threads quando è possibile.
Modulo 4. Sviluppo orientato ai test
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Come scrivere software con tecnologie agili, con lo scopo di ridurre e
tenere sotto controllo i bugs. Discussione di doctest, py.test e unittest.
Esempi di programmazione test driven.*
Consigli di carattere generale
-------------------------------
- testate SUBITO, non debuggate dopo (ad ogni una riga di codice che si
scrive, si testa che funzioni e che non distrugga quello che funzionava
prima).
- scrivete software in maniera che sia testabile (per esempio create
sempre anche una versione non-grafica di un programma grafico, perchè
la versione testuale si testa **molto** più facilmente).
- non abbiate paura a scrivervi un vostro ambiente di test personalizzato.
- tenete conto che unittest e doctest esistono e possono aiutarvi infinitamente
nel gestire i vostri test.
Usare unittest
------------------------------------------------------
Tipicamente con unittest si divide la libreria da testare::
#
def is_number(arg):
"Verifica se la stringa arg è un numero valido"
try:
float(arg)
except ValueError:
return False
else:
return True
#
dal file di test, che convenzionalmente ha un nome che inizia per "test"::
#
import unittest
from isnumber import is_number
class TestIsNumber(unittest.TestCase):
def setUp(self):
print "sto inizializzando"
# test positivi
def test_1(self):
"Testa che '1' è un numero buono."
self.assertTrue(is_number("1"))
def test_2(self):
"Testa che '1.3' è un numero buono."
self.assertTrue(is_number("1.3"))
def test_3(self):
"Testa che '+1.3' è un numero buono."
self.assertTrue(is_number("+1.3"))
def test_4(self):
"Testa che '-1.3' è un numero buono."
self.assertTrue(is_number("-1.3"))
# test negativi
def test_5(self):
"Testa che '1-.3' non è un numero buono."
self.assertFalse(is_number("1-.3"))
def test_6(self):
"Testa che 'à non è un numero buono."
self.assertFalse(is_number("a"))
def test_7(self):
"Testa che '42' è un numero buono."
self.assertTrue(is_number("42"))
def tearDown(self):
print "Sto chiudendo quello che c'è da chiudere"
if __name__ == "__main__":
unittest.main()
#
Eseguire i tests con l'opzione verbose da::
$ python test_isnumber.py -v
Testa che '1' è un numero buono. ... sto inizializzando
Sto chiudendo quello che c'è da chiudere
ok
Testa che '1.3' è un numero buono. ... sto inizializzando
Sto chiudendo quello che c'è da chiudere
ok
Testa che '+1.3' è un numero buono. ... sto inizializzando
Sto chiudendo quello che c'è da chiudere
ok
Testa che '-1.3' è un numero buono. ... sto inizializzando
Sto chiudendo quello che c'è da chiudere
ok
Testa che '1-.3' non è un numero buono. ... sto inizializzando
Sto chiudendo quello che c'è da chiudere
ok
Testa che 'à non è un numero buono. ... sto inizializzando
Sto chiudendo quello che c'è da chiudere
ok
Testa che '42' è un numero buono. ... sto inizializzando
Sto chiudendo quello che c'è da chiudere
ok
----------------------------------------------------------------------
Ran 7 tests in 0.001s
OK
Questo mostra che i metodi ``setUp`` e ``tearDown`` vengono chiamati
*per ogni test*, quindi tutti i test si svolgono in un ambiente pulito.
E' normale avere un file di test più lungo della libreria da testare.
E' possibile specificare le eccezioni aspettate::
#
import unittest
def divide(a, b):
return a/b
class TestIsNumber(unittest.TestCase):
def test_1(self):
"Divide 4/2"
self.assertEqual(divide(4,2), 2)
def test_2(self):
"Divide 4/0"
self.assertRaises(ZeroDivisionError, divide, 4, 0)
if __name__ == "__main__":
unittest.main()
#
$ python test_exc.py -v
Divide 4/2 ... ok
Divide 4/0 ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
Usare doctest
-----------------------------------
L'uso più semplice, eseguire i doctest che si trovano nelle stringhe
di documentazione di un modulo::
#
def sum12():
"""Questa funzione ritorna la somma di 1 + 2::
>>> sum12()
3"""
return 1+2
if __name__ == "__main__":
import doctest; doctest.testmod()
#
Ecco come eseguire i tests con l'opzione verbose::
$ python esempio_banale.py -v
Trying:
sum12()
Expecting:
3
ok
1 items had no tests:
__main__
1 items passed all tests:
1 tests in __main__.sum12
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Eseguire i doctest che si trovano in un file di testo separato (nuova
funzionalità di Python 2.4)::
#
import doctest
if __name__== "__main__":
doctest.testfile("test_isnumber.txt")
#
Contenuto di 'test_isnumber.txt'::
Questa è la documentazione della funzione isnumber
====================================================
Esempi di uso:
>>> from isnumber import is_number
>>> is_number("1")
True
>>> is_number("1.3")
True
>>> is_number("+1.3")
True
>>> is_number("-1.3")
True
>>> is_number("1-.3")
False
>>> is_number("a")
False
>>> is_number("42")
True
>>> 1/0
Traceback (most recent call last):
kkkkdjjfkf
ZeroDivisionError: integer division or modulo by zero
Eseguire i tests::
$ python doctest_runner.py -v
Trying:
from isnumber import is_number
Expecting nothing
ok
Trying:
is_number("1")
Expecting:
True
ok
Trying:
is_number("1.3")
Expecting:
True
ok
Trying:
is_number("+1.3")
Expecting:
True
ok
Trying:
is_number("-1.3")
Expecting:
True
ok
Trying:
is_number("1-.3")
Expecting:
False
ok
Trying:
is_number("a")
Expecting:
False
ok
Trying:
is_number("42")
Expecting:
True
ok
Trying:
1/0
Expecting:
Traceback (most recent call last):
kkkkdjjfkf
ZeroDivisionError: integer division or modulo by zero
ok
1 items passed all tests:
9 tests in test_isnumber.txt
9 tests in 1 items.
9 passed and 0 failed.
Test passed.
Confrontare la leggibilità di ``test_isnumber.txt`` con la leggibilità
di ``test_isnumber.py``, basato su unittest. Leggetevi il mio seminario su
doctest (in allegato) per convincervi che doctest è il migliore. Anche perchè
i doctest possono essere convertiti in unittest automaticamente, a partire
da Python 2.4
Per convertire i test contenuti in 'mymodulè da doctest a unittest::
import doctest, unittest, mymodule
if __name__== "__main__":
suite = doctest.DocTestSuite(mymodule)
unittest.TextTestRunner(verbosity=2).run(suite)
E' anche possibile contenere i tests contenuti in un file di
tipo testo da doctest a unittest, vedere la documentazione.
Correntemente, doctest non ha un meccanismo predefinito corrispondente
ai metodi ``setUp`` e ``tearDown`` di unittest, ma potete impostarlo a mano.
Un esempio di programma sviluppato in maniera incrementale
----------------------------------------------------------
Il seguente esempio svolto al corso intendeva dimostrare:
1. Come si sviluppa un programma in maniera incrementale (ad ogni nuova riga di
codice si fa una verifica immediata del funzionamento dell'insieme);
2. Come si fanno scelte architetturali in maniera tale da assicurare la
testabilità del prodotto finale in maniera semplice ed automatica;
3. Come sfruttare la libreria standard di Python al meglio, usando il
modulo cmd;
4. Come dividere il codice in metodi pubblici, metodi di utilità e
metodi di debugging;
5. Come assicurarsi che il programma venga chiuso propriamente, anche
nel caso di eccezioni impreviste e imprevedibili;
::
# -*- encoding: latin-1 -*-
import os, cmd, subprocess, lib, time # lib fissa os.kill
from pywintypes import error as WindowsProcessDidNotStartCorrectly
MUSICDIR = "C:/Documents and Settings/micheles/Desktop/Music"
def play_song(name):
po = subprocess.Popen(["mplay32", "/play", "/close", name])
return po.pid
class InterpreteDiComandi(cmd.Cmd):
cwd = MUSICDIR
prompt = "Player> "
def preloop(self):
self.process_list = []
os.chdir(MUSICDIR)
def do_play(self, arg):
"Suona una canzone"
if not arg:
print "Per favore scrivi il nome di una canzone!"
else:
self.process_list.append(play_song(arg))
def do_quit(self, dummy):
"Esce dall'interprete."
return True
def safe_kill(self, pid):
try:
os.kill(pid)
except WindowsProcessDidNotStartCorrectly, e:
print e
def do_kill(self, arg):
"Uccide il player"
try:
pid = self.process_list.pop()
except IndexError:
print "Hai già ucciso tutti i processi!"
else:
self.safe_kill(pid)
def postloop(self):
for pid in self.process_list:
self.safe_kill(pid)
print "Ho ucciso tutto!"
os.chdir(os.path.dirname(os.path.abspath(__file__)))
# metodi che possono essere utili per il debugging
def do_print_dir(self, arg):
"Comando utile per il debugging"
print self.cwd
def do_raise_exc(self, dummy):
raise RuntimeError("Tanto per vedere che succede")
def do_sleep(self, arg):
"utile per vedere quello che i test automatici stanno facendo"
time.sleep(int(arg))
i = InterpreteDiComandi()
try:
i.cmdloop()
finally: # assicura la chiusura anche in caso di eccezione
i.postloop()
Nel caso regolare (senza eccezioni impreviste) .postloop è chiamato 2 volte,
ma questo non fa danno.
Questo potrebbe essere il codice corrispondente ad un test automatico::
$ more test_player.cmd
play
sleep 2
play 2. Croi Croga.mp
sleep 2
play 2. Croi Croga.mp3
sleep 2
kill
sleep 2
quit
(la prima canzone non esiste, in modo da poter vedere come viene segnalato
l'errore) che verrebbe eseguito così::
$ python player.py < test_player.cmd
Modulo 5: Design, documentazione e manutenzione di librarie
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Pratiche di programmazione "in the large". Moduli, packages, strumenti di
documentazione e di installazione. Applicazioni pratiche di principi generali
quali disaccoppiamento, modularità, non duplicazione del codice.*
La filosofia del Python
---------------------------------------
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you are Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Principio del disaccoppiamento
-------------------------------------
Questo è il principio guida di tutta la programmazione e non solo, tutto
il resto nasce di conseguenza.
Il principio dice: **cercate di disaccoppiare il più possibile
le componenti del vostro sistema**.
*Un componente si dice disaccoppiato se
può essere rimosso o sostituito senza danneggiare il resto del sistema.*
Per esempio:
- disaccoppiare l'ambiente di sviluppo dall'ambiente di esecuzione (è più
sicuro lanciare un programma dalla riga di comando che dall' IDE; un
debugger disaccoppiato come pdb è più sicuro di un debugger integrato
come quello di PythoWin, un editor vero è più sicuro dell'editor dell'
IDE, etc.)
- disaccoppiare l'interfaccia grafica: il programma deve poter funzionare
senza di essa, o sostituendo il GUI toolkit con un altro.
- i threads vi accoppiano tutto, se uno va male può bloccare tutti gli
altri: evitateli se potete.
- l'ereditarietà vi accoppia il parente al figlio (per sapere quello che
fa il figlio bisogna andare a vedere quello che fanno il padre, il nonno,
il bisnonno, il trisavolo, etc.). Evitatela se potete.
- la modularità è un modo concreto per applicare il principio del
disaccoppiamento al codice: funzioni che logicamente vanno insieme,
vanno messe in un modulo comune, funzioni separate vanno separate.
Se c'è un problema nel modulo X, questo non deve interferire con
il modulo Y (almeno in un mondo ideale). La modularità rende anche
più semplice rimpiazzare un modulo sbagliato o vecchio con uno
corretto o più recente.
- la non-duplicazione del codice è una conseguenza della modularità:
avendo raggruppato le funzioni di uso generale in un modulo comune,
si acquista in concisione, semplicità, leggibilità, debuggabilità,
si evita di correggere lo stesso errore più volte, e in generale tutto
diviene più facile da mantenere.
Principio del KISS
--------------------------------------
*Keep it simple, stupid*. In Italiano: *non facciamoci del male*.
Se potete fare le cose in maniera semplice, fatele in maniera semplice.
Chiedetevi sempre se una cosa vi serve veramente oppure no.
Importanza di avere un prototipo
------------------------------------------
Non c'è nulla di più sbagliato che partire scrivendosi l'architettura di
un progetto complesso sulla carta. Si parte sempre scrivendo un prototipo,
*testato sul campo di battaglia*, che magari ha l'1% delle funzionalità
che vi interessano, ma che *funziona*. A quel punto è possibile decidere
l'architettura e il prototipo diventerà il core e la parte più testata
e sicura della vostra applicazione.
Il prototipo/core deve *rimuovere tutto l'inessenziale*. Più povero è,
meglio è.
Moduli e packages
-------------------------------------------
Qualunque file Python può essere visto come un modulo. Un package invece,
è una directory contente un file ``__init__.py`` che contiene il codice di
inizializzazione (``__init__.py`` può benissimo essere vuoto, oppure può
consistere solo di 'import' dei moduli di quel package).
Importare un package non importa automaticamente tutti i suoi moduli,
a meno che questo non sia detto esplicitamente nell' ``__init__.py``.
I package possono essere nidificati senza limite.
Le variabili globali di un modulo sono locali a quel modulo, quindi
non c'è mai il rischio di fare danni (a meno che un modulo non importi
esplicitamente una variabile corrispondente ad un oggetto mutabile e
la cambi esplicitamente).
E' possibile mettere un package nel Python path automaticamente per
tutti gli utenti listando il nome del package in un file ``.pth`` nella
directory site-packages (vedere la documentazione di distutils).
Come si documenta una libreria Python
-------------------------------------------------
Python è un linguaggio "self-documenting" grazie alle doctrings.
Usatele. In più guardate come è documentato un progetto Python
tipico (io suggerisco di guardare a *docutils*) e copiate le convenzioni
usate lì. Ci sono dei file standard come il README.txt, l'HISTORY.txt,
il BUGS.txt, una directory docs, una directory test, un file setup.py,
etc.
Tenete presente che grazie a *doctest* potete inserire dei test automatici
all'interno della vostra documentazione.
Modulo 6: Domande estemporanee
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Risponderò alle domande dell'audience, anche al di fuori dal programma,
se di interesse generale*.
Come funziona 'import'
---------------------------------
Se un modulo è già stato importato e chiamate 'import' una seconda
volta, il modulo NON viene importato due volte. Questo può essere
un problema nell'interprete interattivo. Se importate un modulo,
poi lo modificate e lo importate di nuovo, vi resterà in memoria
la copia *vecchia*, quella non modificata. La soluzione è usare
'reload', che vi importerà veramente la nuova versione.
Come funziona '__del__'
---------------------------------------------
>>> a = 1
>>> del a
vi cancella il nome dal namespace:
>>> a
Traceback (most recent call last):
File "", line 1, in ?
NameError: name 'a' is not defined
Tuttavia l'oggetto cui si riferisce *non viene cancellato immediatamente*.
Verrà cancellato dal garbage collector *quando serve*, ma soltanto
*se non vi sono altre referenze* all'oggetto da qualche altra parte.
Inoltre per oggetti C, dovrete definirvi un metodo ``__del__`` custom
che ha accesso all'oggetto a livello C. Tenete anche conto che tipicamente
il debugger tiene referenze agli oggetti aggiuntive, quindi in modalità
di debugger i problemi con oggetti non cancellati peggiorano.
Ecco un esempio di un metodo ``__del__`` custom piuttosto banale:
>>> class C(object):
... def __del__(self):
... print 'Hai usato del'
...
>>> c = C()
>>> del c
Hai usato del
Il metodo ``__del__`` viene chiamato automaticamente all'uscita
dall'interprete:
>>> class C(object):
... def __del__(self):
... print 'Hai usato del'
...
>>> c = C()
>>> per uscire dall'interprete
Hai usato del
In principio, all'uscita del programma Python
*dovrebbe chiamare ``__del__`` automaticamente anche se vi sono eccezioni*::
#
class C(object):
def __del__(self):
print "Hai chiamato del"
c = C()
raise RuntimeError("Ahi ahi!")
#
$ python del_with_exc.py
Traceback (most recent call last):
File "del_with_exc.py", line 6, in ?
raise RuntimeError("Ahi ahi!")
RuntimeError: Ahi ahi!
Hai chiamato del
Tuttavia per oggetti ``C`` ed eccezioni le cose possono essere delicate ed
è sempre meglio chiamare ``del`` *esplicitamente*, magari in un
``try .. finally``.
Che differenza c'è fra variabili di classe e di istanza
---------------------------------------------------------
Questo esempio dovrebbe chiarire la differenza:
>>> class C(object):
... pippo = 'sono una variabile di classe'
... def __init__(self):
... self.poppi = 'sono una variabile di istanza'
...
Le variabili di classe sono le stesse per tutte le istanze:
>>> c1 = C()
>>> c2 = C()
>>> c1.pippo
'sono una variabile di classe'
>>> c2.pippo
'sono una variabile di classe'
Se cambio una variabile di classe, cambia per tutte le istanze, anche
per quelle che sono state create *prima* del cambiamento:
>>> C.pippo = 'adesso la cambio'
>>> c1.pippo
'adesso la cambio'
>>> c2.pippo
'adesso la cambio'
Lo stesso vale per i metodi, che non sono altro che variabili di classe.
Le variabili di istanza invece sono indipendenti:
>>> c1.poppi
'sono una variabile di instanza'
>>> c2.poppi
'sono una variabile di instanza'
>>> c1.poppi = 'cambio la variabile dell'istanza c1'
>>> c1.poppi
'cambio la variabile dell'istanza c1'
>>> c2.poppi # questa rimane quella che era prima
'sono una variabile di instanza'
Che cosa sono i metodi che iniziano con "__"
------------------------------------------------
Sono metodi protetti. Sono utili per evitare rogne con l'ereditarietà.
Per capire il problema dò un esempio semplice qui di seguito.
Normalmente l'ereditarietà accoppia il padre con il figlio, nel senso che
il figlio può sovrascrivere i metodi del padre, e i metodi del padre andranno
automaticamente a chiamare i metodi del figlio::
class B(object): # padre
def __init__(self):
self.hello()
def hello(self):
print "hello!"
class C(B): # figlio
def hello(self):
print "cucu!"
b = B() # stampa 'hello!'
c = C() # stampa 'cucu!'
In questo esempio l'__init__ del padre chiama l'hello del figlio. Tuttavia,
a volte uno vuole essere sicuro che l'__init__ del padre chiami l'hello
del padre, e non quello del figlio (sempre per via del principio del
disaccoppiamento). Per questo ci sono i *metodi protetti*::
class B(object):
def __init__(self): # call the '__hello' method in THIS class
self.__hello()
def __hello(self):
print "hello!"
class C(B):
def __hello(self): # won't be called by B.__init__
print "cucu!"
b = B() # stampa 'hello!'
c = C() # stampe 'hello!'
I metodi protetti di tipo ``__`` non vanno confusi con i metodi
privati di tipo ``_`` (un solo underscore):
1. i metodi privati non andrebbero chiamati mai, se non sapendo al 100% quello
che si sta facendo, e solo in caso di errori nel design della libreria che
si sta utilizzando;
2. i metodi protetti possono essere chiamati con tranquillità. Proprio
perchè sono protetti, c'è la garanzia che sovrascrivendoli nella
sottoclasse non si va ad alterare il comportamento ereditato dal padre
(nel nostro esempio l'``__init__`` del padre continuerà a chiamare il
proprio ``__hello``, non l'``__hello`` del figlio). D'altra parte i
metodi definiti nel figlio vedranno solo l'``__hello`` del figlio e
non quello del padre.
3. i metodi protetti non possono essere chiamati accidentalmente, perchè
per esempio ``c.__hello()`` non funziona. Tuttavia è possibile chiamarli,
quindi non sono veramente privati: nel nostro esempio è possibile
chiamare
``c._B__hello`` (``__hello`` del padre) oppure
``c._C__hello`` (``__hello`` del figlio)
Siccome bisogna specificare il nome della classe dove il metodo è definito
non c'è il rischio di sbagliarsi (a meno di non essere masochisti e di
non dare lo stesso nome al padre e al figlio).
I metodi protetti vengono usati raramente. Vedere "Python in a Nutshell"
per maggiori informazioni sul loro uso.
Soluzioni al questionario di ammissione
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Scrivere un programma che testa se una stringa rappresenta un numero
---------------------------------------------------------------------
Soluzione standard (vedere anche il tutorial di Python)::
try:
int(x)
except ValueError:
print "This is not a number!"
Soluzione usando tkSimpleDialog::
from tkSimpleDialog import askfloat
askfloat("Enter a number", "Number:")
Scrivere un programma che lista tutti i files nella directory corrente
------------------------------------------------------------------------
::
for f in os.listdir("."):
print f
Listare tutti i files nelle sottodirectories ricorsivamente
-------------------------------------------------------------
::
for cwd, dirs, files in os.walk("."):
for f in files:
print f
Calcolare lo spazio occupato da tutti i files di tipo .txt in una directory
----------------------------------------------------------------------------
[Soluzione svolta al corso 1]
::
import os
def get_text_files(d):
for cwd, dirs, files in os.walk(d):
for f in files:
if f.lower().endswith(".txt"):
fullname = os.path.join(cwd, f)
size = os.path.getsize(fullname)
yield fullname, size
from operator import itemgetter
print sum(map(itemgetter(1), get_text_files(".")))
Listare i files a seconda delle dimensioni
-------------------------------------------
::
print sorted(get_text_files("."), key=itemgetter(1))
Scrivere un test per verificate che una directory sia vuota
--------------------------------------------------------------
::
def is_empty(d):
if os.listdir(d): return False
else: return True
Sarebbe ridondante scrivere
::
if os.listdir(d) != []:
perchè la lista vuota ha già di per sè un valore booleano "False".
Aprire una finestrella contenente la scritta "hello, world!"
-------------------------------------------------------------
[In maniera portabile]
Soluzione più semplice::
# hellotk.pyw
from tkMessageBox import showinfo
showinfo(message="hello")
Soluzione alternativa::
# hellotk.pyw
import Tkinter as t
root = t.Tk()
l = t.Label(text="hello")
l.pack()
root.mainloop()
Guardatevi sull'help di ActiveState la differenza tra l'estensione ``.py``
e l'estensione ``.pyw`` (dovrebbe essere nelle FAQ).
Scaricare la pagina Web http://www.example.com da Internet
----------------------------------------------------------------
>>> from urllib2 import urlopen
>>> print urlopen('http://www.example.com').read()
Stampare a schermo una tavola delle moltiplicazioni
---------------------------------------------------------
Soluzione senza generatori::
#
# non graphic
N = 10
for i in range(1, N+1):
for j in range(1, N+1):
print "%4d" % (i*j),
print
# HTML
def maketable(iterable, N):
iterable = iter(iterable)
print ""
stop = False
while not stop:
print ""
for j in range(1, N+1):
try:
print "%s | " % iterable.next(),
except StopIteration:
print " | "
stop = True
print "
"
print "
"
import tempfile, webbrowser, os, sys
def showtable(iterable, N):
stdout = sys.stdout # shows how to redirect stdout correctly
fd, name = tempfile.mkstemp(suffix=".html")
sys.stdout = os.fdopen(fd, "w")
maketable(iterable, N)
webbrowser.open(name)
sys.stdout = stdout
showtable((i*j for j in range(1, N+1) for i in range(1, N+1)), N)
#
Soluzione usando i generatori, non HTML::
#
def gentable(N):
for i in range(1, N+1):
for j in range(1, N+1):
yield i*j
def printtable(lst, N):
for i, el in enumerate(lst):
print "%4d" % el,
if (i+1) % 10 == 0:
print
if __name__ == "__main__":
printtable(gentable(10), 10)
#
Trovare tutte le immagini .jpg nel vostro hard disk e mostrarle a schermo in formato ridotto
--------------------------------------------------------------------------------------------
Soluzione svolta al corso 1::
import os, webbrowser
def gentableHTML(iterable, N):
iterator = iter(iterable)
yield ""
yield ""
yield ""
for i in range(N):
yield ""
for j in range(N):
yield ' | \n' % \
iterator.next()
yield "
\n"
yield "
"
yield ""
yield ""
# generatore di file names
def get_files_with_ext(ext_set, d):
"""
ext_set is a set of valid extensions.
"""
assert isinstance(ext_set, set), "%r is not a set!" % ext_set
for cwd, dirs, files in os.walk(d):
for f in files:
name, ext = os.path.splitext(f)
fullname = os.path.join(cwd, f)
if ext.lower() in ext_set and os.path.getsize(fullname):
yield fullname
if __name__ == "__main__":
# genera il file e lo mostra
images = file("images.html", "w")
for el in gentableHTML(get_files_with_ext(set([".png"]),
"C:\\Documents and Settings"), 15):
print >> images, el,
images.close()
webbrowser.open("images.html")
Soluzione più sofisticata, per darvi qualcosa su cui pensare::
#
def get_files_with_ext(ext, d):
"""
ext can be a string or a set of strings; for instance
get_files_with_ext(".png") or get_files_with_ext(set(".png", ".gif"))
"""
if not isinstance(ext, set):
ext_set = set([ext])
for cwd, dirs, files in os.walk(d):
for f in files:
name, ext = os.path.splitext(f)
if ext.lower() in ext_set:
yield os.path.join(cwd, f)
class Picture(object):
"""An example of the utility of __str__"""
def __init__(self, pathname):
self.pathname = pathname
self.name = os.path.basename(pathname)
def __str__(self):
return "" % self.pathname
if sys.platform == 'win32':
drive = "C:\\"
else:
drive = "/"
showtable(map(Picture, get_files_with_ext(".jpg", drive)), N)
#