Le Ufunc, abbreviazione di universal functions, sono uno degli elementi più utili di NumPy quando si lavora con array numerici in Python. Se stai iniziando ora, puoi immaginarle come funzioni progettate per eseguire operazioni in modo rapido e ordinato su tutti gli elementi di un array, senza dover scrivere cicli manuali.
Capire come creare una Ufunc NumPy è utile perché permette di trasformare una normale funzione Python in uno strumento riutilizzabile su array, rendendo il codice più pulito, più espressivo e spesso più comodo da mantenere. In questa guida vedremo il processo in modo semplice, con esempi concreti e un taglio adatto anche a chi è alle prime armi.
Come creare una Ufunc personalizzata in NumPy
NumPy mette a disposizione un metodo molto pratico per costruire una ufunc personalizzata: frompyfunc(). Questa funzione consente di prendere una funzione Python tradizionale e convertirla in una ufunc capace di lavorare su valori singoli oppure su array.
La sintassi di base è semplice:
numpy.frompyfunc(funzione, numero_input, numero_output)
I tre parametri indicano rispettivamente:
- funzione: la funzione Python che vuoi trasformare;
- numero_input: quanti argomenti riceve la funzione;
- numero_output: quanti valori restituisce.
Supponiamo di voler creare una funzione che sommi due numeri. In Python puro potresti scriverla così:
def somma(x, y)
{
return x + y
}
Per trasformarla in una ufunc con NumPy, si utilizza frompyfunc() in questo modo:
import numpy as np
def somma(x, y)
{
return x + y
}
ufunc_somma = np.frompyfunc(somma, 2, 1)
A questo punto ufunc_somma è una vera ufunc e può essere applicata anche agli array:
import numpy as np
def somma(x, y)
{
return x + y
}
ufunc_somma = np.frompyfunc(somma, 2, 1)
array1 = np.array([1, 2, 3, 4])
array2 = np.array([10, 20, 30, 40])
risultato = ufunc_somma(array1, array2)
print(risultato)
Il risultato sarà una sequenza di somme elemento per elemento. Questo approccio è particolarmente comodo quando desideri applicare una logica personalizzata a interi array senza riscrivere la funzione in forma vettoriale.
Va però chiarito un punto importante: frompyfunc() è ottima per imparare e per creare rapidamente una funzione universale, ma non sempre è la scelta più veloce sul piano delle prestazioni. Infatti la funzione di partenza resta una funzione Python, quindi la comodità è alta, mentre il guadagno in velocità rispetto alle ufunc native di NumPy non è sempre equivalente. Per chi comincia, comunque, è uno strumento eccellente per capire il meccanismo.
Un esempio pratico per capire meglio le Ufunc personalizzate
Vediamo un caso leggermente più interessante. Immagina di voler creare una funzione che restituisca una descrizione testuale in base al confronto fra due numeri. Per esempio, se il primo numero è maggiore del secondo, la funzione restituirà una frase specifica.
import numpy as np
def confronta(a, b)
{
if a > b
{
return "maggiore"
}
else
{
return "minore o uguale"
}
}
ufunc_confronta = np.frompyfunc(confronta, 2, 1)
x = np.array([5, 2, 8])
y = np.array([3, 2, 10])
risultato = ufunc_confronta(x, y)
print(risultato)
Questo esempio mostra bene uno dei vantaggi delle ufunc create con frompyfunc(): puoi applicare anche logiche personalizzate e non solo calcoli matematici elementari. È una possibilità molto apprezzata quando si vuole prototipare velocemente oppure quando si desidera mantenere il codice leggibile.
Come controllare se una funzione è davvero una Ufunc
Dopo aver creato una funzione universale, potresti voler verificare se l’oggetto ottenuto è effettivamente una ufunc NumPy. Il controllo è semplice e immediato.
NumPy espone il tipo np.ufunc, che può essere usato insieme a isinstance() per capire se una funzione appartiene a questa categoria.
import numpy as np
def somma(x, y)
{
return x + y
}
ufunc_somma = np.frompyfunc(somma, 2, 1)
print(isinstance(ufunc_somma, np.ufunc))
Se tutto è stato creato correttamente, il risultato sarà True.
Questo controllo è utile in diversi scenari pratici. Per esempio, se stai lavorando su uno script più grande, oppure stai costruendo una piccola libreria personale, verificare il tipo di una funzione può aiutarti a evitare errori e a gestire meglio il comportamento del programma.
Perché verificare il tipo di Ufunc può essere utile
All’inizio può sembrare un passaggio secondario, ma sapere se stai trattando una normale funzione Python o una funzione universale di NumPy cambia il modo in cui la utilizzi. Una ufunc, infatti, è pensata per lavorare bene con gli array e segue una logica molto precisa nell’applicazione elemento per elemento.
Se vuoi fare una verifica più esplicita, puoi anche stampare direttamente il tipo dell’oggetto:
import numpy as np
def somma(x, y)
{
return x + y
}
ufunc_somma = np.frompyfunc(somma, 2, 1)
print(type(ufunc_somma))
In questo modo vedrai chiaramente che l’oggetto appartiene alla classe numpy.ufunc. È un controllo semplice, ma molto utile per prendere familiarità con la struttura interna di NumPy.
Quando conviene usare una Ufunc creata da zero
Non sempre serve creare una funzione universale personalizzata, perché NumPy include già moltissime ufunc pronte all’uso, come quelle per somme, moltiplicazioni, arrotondamenti, confronti e operazioni trigonometriche. Tuttavia, ci sono situazioni in cui costruirne una propria è una scelta sensata.
- Quando hai una logica personalizzata che NumPy non offre già;
- Quando vuoi applicare una funzione Python a un intero array in modo uniforme;
- Quando stai studiando NumPy e vuoi capire meglio il funzionamento delle funzioni universali;
- Quando desideri rendere il codice più leggibile e riutilizzabile.
Per un principiante, il vantaggio principale è soprattutto concettuale: imparare a trasformare una funzione normale in una ufunc aiuta a comprendere meglio l’approccio vettoriale di NumPy, che è alla base di gran parte dell’elaborazione numerica in Python.