Created on 18 Oct 2018 ;    Modified on 18 Dec 2018 ;    Translationenglish

edX: il corso Introduction to Computer Science and Programming Using Python

Siccome mi piace vincere facile, ho seguito il corso Introduction to Computer Science and Programming Using Python che MITx eroga tramite edX. Ecco alcune osservazioni che lo riguardano ...

e il suo certificato :-).

Tipo di contenuto

Visto che è una Introduzione ..., mi aspettavo un contenuto più da scuola superiore che da università.

E in effetti, da un lato devo confermare questa prima sensazione. I contenuti non mi sono sembrati complessi [1].

D'altro canto, il rigore dell'esposizione dei concetti è decisamente centrato: pur senza essere formale, è decisamente preciso, concreto ed efficace.

Quindi si parte praticamente da zero, per dare allo studente gli strumenti di base necessari per farsi le ossa nel mondo dell'IT.

Il programma di massima è il seguente:

  • Python basics;
  • simple programs;
  • structured types;
  • good programming practices;
  • object oriented programming;
  • algorithmic complexity;
  • plotting.

L'uso di Python non è lo scopo del corso, ma lo strumento per apprendere i concetti di base. E questo si capisce notando titoli del tipo: good programming practices, e algorithmic complexity.

Trovo ben strutturata anche la parte relativa alle esercitazioni.

Tutti gli esercizi sono bene affrontabili, e vengono proposti anche in contesti abbastanza articolati.

A prima vista possono intimidire, ma se si prosegue senza scoraggiarsi, si osserva che in realtà il contesto viene organizzato opportunamente per proporre esercizi abbordabili. E per far notare come sia possibile suddividere problemi complessi in unità più semplici, che cooperano tra di loro.

Alcune chicche

E già. Pur essendo avvezzo alla materia, qualche nozioncina simpatica quà e là me l'hanno trasmessa. Qui ne riporto alcune.

Capacità fisica di un sistema di elaborazione

Riguardo la velocità di calcolo. Gli attuali computer sono in grado di eseguire miliardi di operazioni al secondo.

E questo lo so.

Quello cui non ho mai fatto caso è rendermi conto quanto sia elevata questa capacità di calcolo. E l'esempio riportato dal prof. Eric Grimson è interessante:

... se avete una lampada ad una distanza di circa 30 cm, quando l'accendete, nel tempo in cui la luce parte dalla lampada e vi raggiunge, il vostro computer ha eseguito due operazioni ...

E riguardo la dimensione della memoria. Il fatto che in 100 GByte sia possibile registrare circa un milione e mezzo di libri di media lunghezza, è altrettanto impressionante.

Il computer con cui sto scrivendo questo articolo ha una capacità di memorizzazone di oltre 500 GByte: 7,5 milioni di libri! Essere circondato da 7,5 milioni di libri è il mio sogno paradisiaco.

Tipi di conoscenze

Il fatto che esistano conoscenze descrittive [2] ed imperative [3] mi era già noto dai tempi della tesi universitaria, ma sentirmelo ricordare dopo tanti anni di lavoro con procedue imperative mi è valso un nostalgico tuffo nel passato.

Equivalenza dei linguaggi di programmazione

Turing ha dimostrato che con sei operazioni fondamentali (sottolineo: sei), è possibile eseguire un qualunque algoritmo per risolvere problemi calcolabili [4].

I moderni linguaggi di programmazione mettono a disposizione molte più di sei operazioni. Ma sono tutti equivalenti al linguaggio di base inventato da Turing. E, di conseguenza, risolvono tutti la stessa classe di problemi.

Dopo di che: alcuni sono più efficaci in un contesto, piuttosto che in un altro, ma le potenzialità non cambiano.

In input può essere necessario fare casting

Sarà che non uso molto l'istruzione input, ma dimentico sempre che il suo output è sempre una stringa, anche quando si richiede all'utente di digitare un numero. Perciò la necessità di fare casting quando serve.

Il test di uguaglianza dei float può dare problemi

Questo è un concetto che conosce chiunque abbia un po' di esperienza, ma metterlo in evidenza fin dai primi passi non è male. Quindi non eseguire float1 == float2, ma basarsi su abs(float2-float1) < epsilon.

Usare le docstring come specifiche della funzione

Questa mi ha un po' sorpreso. L'importanza dell'uso delle docstring per documentare la funzione è messa in rilevo da tutti.

Che si possa usare la docstring per documentare una specifica d'uso, non vincolante, della funzione stessa, non l'avevo realizzato. Ma ha perfettamente senso!

Quindi, indicare:

  • le assunzioni che il progettista fa per la chiamata della funzione;
  • le conseguenti garanzie riguardo azioni e valori restituiti dalla funzione, se chiamata correttamente.

E' un vero e proprio contratto.

Ricorsione e Torre di Hanoi

Decisamente, il prog. Grimson è un patito della ricorsione.

Personalmente trovo questo strumento molto elegante ma, solitamente, poco utile, in quanto stressa l'uso dello stack.

Però esiste un contesto in cui è veramente notevole: la soluzione del problema della Torre di Hanoi. Senza l'uso della ricorsione, è veramente dura :-)

Operazioni di mutazione delle liste

Questo è un concetto chiaro per chi proviene dal c language, ma per chi è alle prime armi va digerito bene. E ogni tanto fa sbagliare anche chi non è alle prime armi (cioè lo scrivente).

Quindi:

  • sorted(aList) restituisce una copia ordinata di aList;
  • aList.sort() muta sul posto la lista aList, ordinandola in senso crescente;
  • anche aList.reverse() muta sul posto aList, ordinandola in senso decrescente.

Attenzione inoltre al concetto di alias. Ovvero l'assegnazione ad altro nome, che diviene sinonimo della lista. Ad esempio: anotherList = aList, fa sì che anotherList veda la stessa area di memoria di aList.

Mutazione, alias (==sinonimo) e clonazione (ovvero: aList[:]) sono tre concetti da tenere strettamente sotto controllo.

In particolare non mutare una lista mentre la si sta attraversando. Piuttosto usare una copia della lista per fare l'attraversamento, mutando l'originale.

Un'ultima nota riguardo un'operazione che non si vede spesso sulle liste:

for i in range(dim):
    aList += [i]

crea una lista di dim elementi, ognuno di valore pari a dim. Ad esempio, con dim == 10:

>>> print(aList)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Applicare una funzione ripetutamente

Non è una operazione abituale, ma in certi ambiti (data science) è decisamente comune.

In pratica si tratta di applicare una funzione a tutti gli elementi di una lista, restituendo la lista dei valori di ritorno della funzione stessa.

Ma è possibile fare anche il contrario: applicare un argomento ad una lista di funzioni.

Questi giochetti sono conosciuti come higher order programming (HOP per gli amici), e sono facilmente implementabili usando Python [5].

D'altro canto Python permette di fare questo ed altro con la funzione map, che produce un iterabile.

A caccia di errori ...

... utilizzando la bisezione del codice! Questa mi è giunta decisamente nuova.

In pratica si tratta di dividere il codice circa a metà e verificare se l'errore si verifica prima o dopo il punto scelto. Per poi proseguire di conseguenza, come se si stesse facendo una ricerca tramite bisezione di un elemento in un elenco ordinato.

Uso delle asserzioni come programmazione difensiva

Anche questa mi è nuova, ed è interessante.

Il prof. Grimson suggerisce di utilizzare assert per verificare che:

  • siano rispettati i tipi, ed eventualmente il range, dei parametri di input delle funzioni;
  • analogamente per l'output delle funzioni, al fine di evitare la propagazione dell'errore.
Uso della classe per lanciare un metodo d'istanza

Non è usuale, ma si può fare. Ad esempio:

>  class Animal(object):
...    def __init__(self, name):
...        self.name = name
...    def __str__(self):
...        return "I am " + self.name
...
>  prince = Animal("Prince")
>  print(Animal(__str__(prince))
I am Prince

in cui abbiamo chiamato il metodo prince.__str__() passando per la classe dell'istanza.

Generatori

Trovare questo argomento mi ha sorpreso. I generatori non ritengo siano esattamente cose da principianti. Ma devo dire che ha sicuramente senso capire subito in cosa consistono, e quali sono i pro (e i contro).

Comunque la spiegazione, come usuale, è diretta e chiara.

Conclusione

Un corso ab initio, che mi sento di raccomandare a chi vuole avvicinarsi al mondo dell'informatica, senza avere delle basi specialistiche precostituite.

Unico svantaggio: è in lingua inglese. E, al momento in cui scrivo, non vi sono sottotitoli in lingua italiana.

Post scriptum

Questo corso ha un seguito: si tratta di Introduction to Computational Thinking and Data Science, che ho recensito in questo articolo.


[1]Ma in questo posso essere stato ingannato dal fatto che, lavorando da parecchi anni nel settore della tecnologia informatica, alla fine alcuni concetti li ho appresi.
[2]Una conocenza dichiarativa espone un fatto. Ad esempio la frase "Albert Einstein era tedesco" è conoscenza dichiarativa.
[3]Una conoscenza imperativa spiega come fare qualcosa. A esempio, la frase "Rompi il guscio dell'uovo e versane il contenuto in una terrina" è un pezzo di procedura iterativa per produrre una frittata.
[4]Esistono problemi che non possono essere risolti in un numero finito di operazioni. Questi sono i problemi non calcolabili.
[5]Come dimostrato nel corso: vi sono vari esempi di questo tipo.