Essendo IronPython un porting di Python una panoramica del linguaggio è doverosa. Suggerisco di arrivare comunque in fondo al tutorial di Python per capirne almeno le potenzialità (o anche per capire se piace o meno).
Il libro introduce Python abbastanza velocemente, ma è anche giusto così. Si impara facendo, inutile studiarsi a memoria tutta la sintassi e le funzionalità al primo colpo.
Gli articoli precedenti sono l’introduzione a IronPython e il preambolo.
Questi post son scritti in TextMate usando Markdown e poi convertiti in HTML. Ho scoperto che ci metto decisamente meno che scrivendo nell’editor Web.
Python è un linguaggio piuttosto semplice da imparare. Non ha diecimila costrutti e ha poche keyword. Essendo un linguaggio di altissimo livello ha insiti anche vari design pattern che vengono dati per scontati, questo gli permette una notevole ricchezza espressiva.
Per sperimentare con Python potete continuare ad usare la shell di IronPython o se volete sperimentare con la versione ufficiale scaricate l’installer dell’ultima versione della serie 2.x dal sito www.python.org
Panoramica
Siete sicuramente liberi di mischiare i vari stili di programmazione supportati e non obbligati ad incapsulare tutto in classi contenitore sin dall’inizio. Le funzioni hanno perfettamente senso per molti compiti.
Python usa l’indentazione per delimitare i blocchi. Niente parentesi graffe, niente begin…end. Semplicemente l’indentazione che fa parte della sintassi.
Lo standard è di 4 spazi (non tab, 4 spazi). È possibile usare anche il tab ovviamente, basta non mischiare spazi e tab. Consiglio di usare gli spazi perché sono costanti da editor a editor. L’unità tab potrebbe essere impostata diversamente da editor diversi.
È ovviamente case sensitive, e il carattere per i commenti è l’hash, #.
Ha un ricco set di tipi di base (definiti builtin):
- str (stringhe di byte): ‘hello’, “hello”, “”“hello”“”, ”’hello”’
- unicode (stringhe unicode): u’hello’, u”hello”, u”“”hello”“”, u”’hello”’
-
int (numeri interi, 32bit o 64bit autopromossi a long integer): 1
>>> import sys
>>> a = sys.maxint
>>> a
2147483647
>>> type(a)
<type 'int'>
>>> type(a + 1)
<type 'long'>
-
long (long integer, limitati soltanto dalla memoria): 1L
- float (numeri in virgola mobile) -> 1.0, 1.234e-10
- complex (numeri complessi) -> 1j
- list (liste) -> [], [1, 2], [1, “a”]
- tuple (tuple) -> (), (1,), (1, “a”)
- dict (dizionari, hash) -> {}, {1: “a”}
- set (insiemi) -> set(), set([1, a])
- NoneType (null) -> None
- bool (booleani) -> True, False
Stringhe:
- usate gli apici o i doppi apici per le stringhe normali, gli apici tripli per le stringhe che si sviluppano su più righe
- usate gli escape Unix
- le stringhe in Python sono memorizzate come sequenze di byte
- In IronPython le stringhe e le stringhe unicode son la medesima cosa (in .NET non esistono stringhe di byte), la stessa cosa in Python 3.0
- basestring è la super classe di str e unicode, quindi si può usare isinstance(obj, basestring) per compatibilità con Python 2.x
- r’una stringa con \escape’ indica una raw string, i cui escape non vengono intepretati (molto usate nelle regular expressions)
- le stringhe hanno molti metodi utili
L’interazione tra i tipi Python e quelli IronPython avviene in maniera molto semplice. Di default sui tipi non sono abilitati i metodi .NET, per farlo importate il modulo clr nel seguente modo:
>>> s = 'Hello World'
>>> s.ToUpper()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'ToUpper'
>>> s.upper()
'HELLO WORLD'
>>> s
'Hello World'
>>> import clr
>>> s.ToUpper()
'HELLO WORLD'
>>> s
'Hello World'
>>> import System
>>> System.String is str
True
>>> str, System.String
(<type 'str'>, <type 'str'>)
Numeri:
- Le regole usuali di promozione si applicano anche in Python
-
Di base è abilitata la integer division ma basta usare un float per avere la true division o importarla (è di default in Python 3):
>>> 4 / 3
1
>>> 4 / 3.0
1.333333333333333
>>> from __future__ import division
>>> 4 / 3
1.333333333333333
-
Usare // per la truncating division:
>>> 4 // 3
1
>>> 4 // 3.0
1.0
Liste:
- Le liste possono contenere qualsiasi tipo di oggetto e aumentare o diminuire in dimensione automaticamente (al solito, limitate dalla quantità di memoria)
-
Sono tipi sequenza indicizzati per posizione:
>>> a = [1, [], 'a', 4.0]
>>> a[0]
1
>>> a[-1]
4.0
-
Fornire un indice al di fuori dei limiti risulta in un errore:
>>> a[4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
-
Le liste sono mutabili:
>>> a
[1, [], 'a', 4.0]
>>> a[1] = [2, 3]
>>> a
[1, [2, 3], 'a', 4.0]
>>> del a[2]
>>> a
[1, [2, 3], 4.0]
Tuple:
- Come le liste le tuple possono contenere qualsiasi tipo di oggetto, avere qualunque dimensione ed essere indicizzate per posizione
- A differenza delle liste non sono mutabili, ossia non è possibile modificarle in place
- Per questa caratteristica di immutabilità possono essere hashabili e quindi essere usate come chiave in un dizionario (oltre ad essere leggermente più efficienti delle liste)
- Sintassi: (1, [], ‘a’, 4.0)
Dizionari:
- Come gli altri container possono contenere ogni tipo di oggetto e non sono limitati in dimensione se non dalla memoria
- Sono indicizzati per chiave
-
Ecco alcune operazioni che si possono effettuare su di essi:
>>> h = {1: 'a', 'foo': ['bar', 2]}
>>> h
{1: 'a', 'foo': ['bar', 2]}
>>> h['foo']
['bar', 2]
>>> h['bar']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'bar'
>>> h[(1, 2)] = 'tuple indexed value'
>>> h
{(1, 2): 'tuple indexed value', 1: 'a', 'foo': ['bar', 2]}
>>> h.clear()
>>> h
{}
-
Vedi anche: http://www.python.org/doc/2.5.4/lib/typesmapping.html per il resto delle operazioni.
Insiemi:
- I valori di un insieme sono ovviamente unici
-
Controllare la presenza di un elemento in un insieme è più veloce che in una lista o in una tupla
>>> x = set([1, 2, 3, 'a'])
>>> x
set(['a', 1, 2, 3])
>>> 'b' in x
False
>>> 'a' in x
True
-
Non sono ordinati come le tuple e le liste
- Supportano le operazioni insiemistiche
Gli altri tipi che vanno menzionati sono: None (NULL object in altri linguaggi), True e False che sono i valori booleani. In Python il concetto di falso è rappresentato da:
- False e None
- 0, 0.0, 0j
- stringa vuota
- container vuoto (tuple, list, set, dict)
Tutto il resto è considerato vero.
Come ho già ripetuto più e più volte qualsiasi cosa in Python è un oggetto (variabili, funzioni, package, moduli, classi, oggetti, stringhe, ecc ecc.). Gli oggetti Python derivano da object, mentre quelli IronPython da System.Object.
Altra caratteristica peculiare è che non c’è differenza tra value types a reference types. In Python semplicemente esistono solo i reference type. Per essere completi e corretti non esiste nemmeno il concetto di variabile. Esistono oggetti e nomi che referenziano degli oggetti. L’operazione di creazione di una variabile significa fare il binding di un oggetto con un nome.
Ad esempio, dati due nomi a e b fare b = a equivale a fare il binding del nome b all’oggetto a cui punta a. Niente scambio di valori, semplicemente un cambio di reference.
Esiste però una differenza tra oggetti mutabili e immutabili. I primi sono modificabili in place, le operazioni sui secondi invece creano un oggetto diverso da quello originale. Le liste e dizionari sono mutabili, le tuple, le stringhe e i numeri non lo sono.
Costrutti base di Python
Python ha statement ed espressioni (quest’ultime ritornano uno o più valori).
print (usato per stampare stringhe sullo standard output) è uno statement. Nella versione 3.0 è una funzione.
Il costrutto condizionale base è definito dalle keyword if/elif/else:
something = 0
if something == 1:
print "it's one"
elif something == 2:
print "it's two"
else:
print "not one or two"
Esiste anche il while ma non il ciclo con controllo in coda (do/while). Esiste il for che funziona su tutto ciò che supporta il protocollo iterativo (container, stringhe, file, stream, ecc. ecc):
>>> seq = range(4)
>>> for i in seq:
... print i,
...
0 1 2 3
Le funzioni sono usate per raggruppare istruzioni ed espressioni ed eventualmente ritornare valori (se una funzione non ritorna alcunché, ritorna None implicitamente):
>>> def sum(a, b):
... return a + b
...
>>> sum(1, 3)
4
>>> sum('a', 'b')
'ab'
>>> sum(1.0, 3.0)
4.0
>>> sum(['a', 'b'], ['d'])
['a', 'b', 'd']
Come potete vedere il duck typing qui funziona alla grande perché l’operazione a + b invoca un metodo sugli oggetti e questi rispondono all’operazione di somma in maniera corretta. Se provate a sommare numeri e stringhe (operazione proibita) la chiamata di sum() risulterebbe in un errore. Per i più curiosi:
>>> 1 + 2
3
>>> (1).__add__(2)
3
La seconda operazione è ciò che l’interprete chiama quando viene fatta l’operazione di somma su un numero. L’elenco completo lo trovate nella documentazione.
Nelle funzioni tra le altre cose (leggete il tutorial per tutti i dettagli) potete usare anche argomenti di default (chiamati keyword arguments in Python):
>>> def p(a, b=1):
... print a, b
...
>>> p('a')
a 1
>>> p('a', 'b')
a b
>>> p('a', b=2)
a 2
Esiste una tonnellata di funzioni builtin che possono facilitare la programmazione. Tipo: abs, max, min, enumerate, len, cmp, isinstance, open (per i file), range, reversed, sorted, sum, zip… L’elenco completo è qui: http://www.python.org/doc/2.5.4/lib/built-in-funcs.html
Ovviamente Python supporta anche le classi (e l’ereditarietà multipla, ma evitatela se potete):
>>> class Sample(object):
... def something(self):
... return 'something'
...
>>> s = Sample()
>>> s.something
<bound method Sample.something of <__main__.Sample object at 0x71230>>
>>> s.something()
'something'
object è la classe base, something è un metodo che prende esplicitamente come primo argomento l’istanza (chiamata self, tipo il this implicito in C#).
Il costruttore si chiama __init__
in Python:
>>> class Sample(object):
... def __init__(self, value):
... self.value = value
...
>>> s = Sample('3')
>>> s.value
'3'
Le classi ovviamente possono avere i loro attributi. È anche possibile intervenire durante la creazione di una classe usando il metodo speciale __new__
e le metaclassi ma non è argomento da introduzione :-D
Nelle classi Python tutto è public di default, usate la convenzione _attribute
per indicare che qualcosa fa parte dello stato interno della classe.
Feature aggiuntive
Python ovviamente supporta la gestione di eccezioni (con le keyword try/except/finally e il raise):
>>> try:
... 'a' + 3
... except TypeError:
... print "catched"
... finally:
... print "cleanup"
...
catched
cleanup
except senza una specifica classe cattura qualsiasi cosa, per specificare più eccezioni da catturare usate le parentesi: except (OSError, TypeError)
.
In IronPython le eccezioni sono wrappate in eccezioni Python ed esiste una corrispondenza tra le eccezioni .NET e quelle Python in modo tale che possiate usare quest’ultime per catturare le prime.
Python ha uno scoping lessicale e supporta le closure. Esempio:
>>> a = 3 # global scope
>>> def f():
... print a # take a from the global scope
...
>>> def g():
... a = 4 # local scope
... print a
...
>>> f()
3
>>> g()
4
>>> def makeAdder(a):
... def adder(b):
... return a + b
... return adder
...
>>> adder = makeAdder(5)
>>> adder(4)
9
>>> adder(1)
6
Un’altra feature molto interessante ed usata (credo presa in prestito da Haskell ma non sono sicuro) sono le list comprehension:
>>> a = [1, 2, 3, 4, 5]
>>> b = [x * 2 for x in a]
>>> b
[2, 4, 6, 8, 10]
>>> c = [x * 2 for x in a if x % 2 == 0]
>>> c
[4, 8]
Esse permettono di fare operazioni sugli elementi di un container producendo liste con una sintassi molto concisa. Da notare che non esiste list comprehension che non possa essere espressa da un ciclo for.
Python supporta anche le funzioni anonime (lambda functions):
>>> doubler = lambda x: x * 2
>>> doubler(3)
6
>>> doubler('a')
'aa'
doubler è una funzione che moltiplica per due l’argomento passatogli. Equivale a:
>>> def doubler(x):
... return x * 2
Le funzionalità possono essere raggruppate in moduli (un modulo è un file di sorgente Python, tipo something.py). Un gruppo di moduli correlati tra loro forma un package (altro non è che una cartella con un modulo speciale chiamato __init__.py
). È possibile importare moduli e/o package con una delle seguenti forme:
import something # something is a module
from something import something_else # something_else can be anything
from something.something_inside import stuff # something is a package
from something import something_else as else # aliasing
from something import * # import everything, do not use unless you have a reason
L’operazione di import di un modulo in breve equivale a: controllare che non sia presente nell’oggetto sys.modules
(sys è un modulo standard), eseguirne il contenuto, creare l’oggetto modulo corrispondente ed eventualmente memorizzarlo in sys.modules
.
Le docstring vengono usate per commentare il codice, possono stare su una o più linee e vengono memorizzate nell’attributo __doc__
dell’oggetto commentato:
>>> def f():
... " Documentation for f "
... pass
...
>>> print f.__doc__
Documentation for f
È possibile usare anche la funzione help(f)
per estrarne il contenuto durante le sessioni interattive. Le docstring vengono anche analizzate dai tool automatici di documentazione.
Infine Python possiede una enorme libreria standard, solo in parte utilizzabile da IronPython per via della limitazione sui moduli scritti in C, anche se esiste un progetto di nome IronClad che cerca di ovviare al problema. La lista dei moduli è come sempre nella documentazione: http://www.python.org/doc/2.5.4/modindex.html
Il libro non va nel dettaglio di molte feature di Python e alcune non le menziona nemmeno ma diventerebbe un libro nel libro se dovesse affrontarle tutte, quindi è comprensibile. Di documentazione online per studiare Python ce n’è a iosa quindi basta cercare. Se avete dubbi chiedete pure :-)