Funzioni

In Python le funzioni sono oggetti di base ( "first-class objects") : l'istruzione def le crea ed assegna loro un nome, che e' un riferimento. Al solito si usano i due punti ed il rientro (indentation) per delimitare il corpo della funzione. Gli argomenti, fra parentesi tonde, seguono il nome della funzione; alla chiamata la funzione viene eseguita, producendo un oggetto, che e' il risultato della funzione e che viene restituito con un'istruzione "return" . In caso l'istruzione "return" non compaia nella funzione, viene restituito l'oggetto vuoto: "None".

La sintassi per la definizione di una funzione e' del tipo:

def nomefunzione(a,b,c):
    '''  docstring:
    descrizione funzione
    '''
    d=a
    e=b+c
    return d+e

Per chiamare la funzione si utilizza una sintassi del tipo:

nomefunzione(r,s,t)
g=nomefunzione(r,s,t)   : qui il risultato della funzione e' assegnato a 'g'

All'inizio di una funzione puo' esserci una stringa multilinea che descrive la funzione Questa viene conservata nella variabile __doc__

def square(x):
   " " "
   Questa funzione eleva
   un numero al quadrato
   " " "
   return x*x

Le funzioni, come oggetti, hanno attributi, cui ci si puo' riferire con

nomefunzione.attributo

Alcuni attributi standard delle funzioni sono:

__doc__ : stringa descrittiva

__name__ : nome della funzione

dir(f) : mostra dizionario degli attributi della funzione f

Il nome della funzione e' semplicemente un riferimento alla funzione, si distingue dalla chiamata alla funzione, ove devono apparire le parentesi tonde dopo il nome. Il nome puo' stare in una lista, essere passato in argomento a funzioni, comparire nell'istruzione return, essere chiave di dizionari; solo le parentesi tonde dopo il nome indicano che la funzione va eseguita.

Per vedere se un nome e' un riferimento ad una funzione si puo' usare la funzione "callable(nome)", che restituisce True se l'oggetto cui ci si riferisce ha l'attributo "__call__", ovvero se e' una funzione.

Qui sotto vediamo, come esempio, una funzione che crea funzioni (function factory):

def maker(N):                 # funzione "factory"
        def action(X):        # qui definisce una funzione
            return X ** N
        return action

fa=maker(2)         # creo una funzione fa che fa il quadrato
fb=maker(3)         # creo una funzione fa che fb il cubo

fa(10)         # produce 10*10    =>  100
fb(10)         # produce 10*10*10 => 1000

Argomenti

Gli argomenti sono passati per assegnazione: in Python le variabili sono riferimenti ad oggetti; nel passaggio degli argomenti, alla variabile nella funzione viene assegnato lo stesso oggetto della variabile corrispondente nella chiamata; cioe' viene fatta una copia dei riferimenti agli oggetti.

I tipi delle variabili vengono definiti solo all'assegnazione dei riferimenti, per cui una funzione, a priori, non sa quali sono i tipi degli argomenti ed eventuali inconsistenze producono errori solo quando la funzione viene eseguita. In questo modo Python implementa naturalmente il polimorfismo, cioe' una stessa funzione puo' essere usata per dati di tipo diverso. Ad esempio la funzione:

def somma(a,b):
    return a+b

se chiamata con: somma(3,2) produrra' 5, se chiamata con stringhe come argomenti: somma('aa','bb') restituira' la stringa 'aabb'. Se chiamata come: somma('a',1) produrra' un errore.

Riassegnare le variabili in argomento entro la funzione non ha effetti sul chiamante, ma gli oggetti mutabili possono essere cambiati nelle funzioni, operando sui loro riferimenti.

Le funzioni possono avere valori di default per gli argomenti. Ad esempio una funzione definita con:

def func(a='uno'):

puo' essere chiamata semplicemente con:

func()

ed il suo argomento a sara' il default: la stringa 'uno' ; oppure con

func('due')

ed il suo argomento a sara' la stringa 'due'

Una funzione puo' anche essere chiamata dando valori ai parametri per nome (keyword arguments), con una sintassi tipo:

func(a='sei')

in questo caso alla variabile "a" entro la funzione, viene assegnata la stringa "sei".

Una funzione puo' essere definita in modo che i suoi argomenti siano visti, entro la funzione, come una tupla o come un dizionario; le definizioni della funzione avranno in questi casi rispettivamente la sintassi:

def func(*nome):

def func(**nome):

Nel caso del dizionario gli argomenti sono passati per nome ed i nomi diventano le chiavi del dizionario.

Questi modo di passare gli argomenti possono essere combinati, in questo caso, nelle chiamate e nella funzione, vanno prima gli argomenti posizionali, poi quelli che finiscono in una tupla, ed infine, con passaggio per nome, quelli che finiscono nel dizionario:

def func(a,b,c):  # esempio di funzione
    print(a)
    print(b)
    print(c)


func(1,2,3)           # chiamata con argomenti passati per posizione
func( b=2,a=1,c=3)    # argomenti passati per nome
func(1,c=3,b=2)       # argomenti passati per posizione, quello che resta per nome

def func(*a):
      print(a)
      '''
      Qui in a finisce una tupla di argomenti,
      la chiamata puo' avere numero variabile di argomenti
      func(1,2,3) stampa la tupla: (1,2,3)
      '''


def func(**d):
      print(d)
      '''
      Qui gli argomenti finiscono in un dizionario
      gli argomenti sono passati per nome
      ed i nomi delle variabili sono le chiavi
      func(a=1,b=2,c=3) stampa:   {'a': 1, 'c': 3, 'b': 2}
      '''


def func(a,*b,**d):
      print(a)
      print(b)
      print(d)
      '''
      Vanno prima gli argomenti posizionali,
      poi quelli per la tupla, infine quelli per il dizionario.
      Chiamata come :  func(1, 2,3,4, s=10,q=20 )
      stampa:  a=1  ;  b=[2,3,4] ; d={s:10,q:20}
      '''

In Python3 abbiamo anche funzioni con argomenti passati per nome dopo quelli che finiscono in una lista:

def func(a,*b,c):

Qui l'ultimo argomento che puo' essere data solo per nome"
con chiamata:   func(1,2, 3, c=40) ,
ed avremo a==1 ; b==[2,3] ; c==40

Anche le chiamate possono contenere liste o dizionari:

func(*a)  : spacchetta l'iterabile a in modo implicito
func(**d) : spacchetta il dizionario in: key1=val1, key2=val2 ..
            Se il dizionario e': {'key1':1,'key2':2,'key3':3}
            la chiamata equivale a:  func(key1=1,key2=2,key3=3);
            e' come una chiamata per nome, con
            key1,key2,key3 che sono le variabili nella funzione.

Valori restituiti

La funzione restituisce un valore specificato nell'istruzione return. Se non viene eseguita l'istruzione return il valore restituito dalla funzione e' l'oggetto speciale di nome: "None", che e' per definizione un oggetto vuoto. Una funzione puo' restituire una tupla, con la sintassi tipo:

return a,b,c

Analoga a "return" e' la funzione "yeld", che ritorna un valore al chiamante; ma la volta successiva che la funzione viene chiamata l'esecuzione parte da dopo l'istruzione yeld. In questo modo si possono implementare iteratori. La sintassi e':

yeld a

Campo di validita' della funzione (scope della funzione)

Una funzione e' valida dal punto del programma in cui si incontra in poi; quando la funzione viene incontrata viene "eseguita", nel senso che il suo nome (che in realta' e' un riferimento) diviene valido ed ad esso sono associate le operazioni contenute nel corpo della funzione. In questo modo e' possibile definire una funzione in modo diverso a seconda del flusso del programma: ad esempio:

if a>b:
   def func(a,b):
      return a-b
else:
   def func(a,b):
      return b-a

Queste istruzioni definiscono la funzione "func" come la differenza fra il piu' grande dei due valori in a e b. A seconda dei casi la funzione e' definita in modo diverso.

Una funzione puo' essere definita entro una funzione, ed allora e' vista solo li'.

Campo di validita' delle variabili (scope delle variabili)

Una variabile definita in una funzione non e' vista da fuori della funzione. Puo' avere stesso nome di una variabile esterna senza confusione.

Una variabile definita nel blocco in cui la funzione e' chiamata e' vista entro la funzione, ma non puo' essere modificata entro la funzione, a meno che non sia definita "global" entro la funzione.

Se, entro una funzione, una variabile e' definita come global sara' vista anche nel blocco in cui la funzione e' chiamata. Ma in ogni caso una variabile e' locale al file in cui si trova.

La dichiarazione di global e' del tipo:

global a,b,c

Questa regola di scope e' chiamata LEGB: Local, Enclosing, Global, Build-in ed e' il modo di cercare i nomi di Python

In Python3 una variabile dichiarata "nonlocal" in una funzione:

nonlocal a,b

e' definita nell'ambito del blocco superiore, deve gia' esistere nel blocco superiore, non e' vista oltre e non e' global. E' usata nelle funzioni definte entro funzioni.

Funzioni lambda

Sono funzioni di una sola istruzione, senza nome, con sintassi:

lambda argomento1,argomento1,argomento3: espressione

Esempio:

f= lambda x,y : x+y

f(2,3)    produce 5

Le Lambda sono usate in contesti particolari, ove e' comodo mettere una piccola funzione in una sola riga, ad esempio per costruire una lista od un dizionario di funzioni:

L=[ (lambda  x: x+x ) , ( lambda x: x*x) ]

for f in L :
  print f(3)      # Produce : 6 , 9

Esempio: dizionario di funzioni:

op={'somma':lambda *a: sum(a) , 'massimo':lambda *a: max(a)}

op['somma'](10,20,30)    #  produce: 60

op['massimo'](10,20,30)  #  produce: 30

Decorators

Sono funzioni che prendono in argomento una funzione, ci fanno modifiche, aggiunte, o fanno cose accessorie, poi restituiscono la funzione modificata:

def decname(funcname,a,b,c):
   ... operazioni varie con la funzione: funcname
   return funcname

C'e' una sintassi abbreviata quando si vuole applicare un decoratore ad una funzione:

@nomedecoratore
def f()
     .....

Questo e' equivalente a definire la funzione f e poi applicargli il decoratore con:

f=nomedecoratore(f)

Esempio:

# creo un decoratore
def decor1(func1):
   func1.a=33
   func1.autore="io"
   return func1

#la uso per fare aggiunte a una funzione

@decor1
def unafunc(c,d):
    r=c+d+unafunc.a
    print('somma: ',r,"author: ",unafunc.autore)

#utilizzo la funzione:

unafunc(1,2)
somma:  36 author:  io