Vai al contenuto principale

Tutorial di programmazione Julia per principianti

Un tutorial introduttivo completo che ti aiuterà a padroneggiare le basi di Julia. Scopri operatori, istruzioni condizionali, lavoro con i DataFrame e altro ancora.
Aggiornato 29 set 2026  · 13 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

Il linguaggio di programmazione Julia è un linguaggio per il calcolo scientifico annunciato per la prima volta nel 2012. Da allora, Julia è diventato un serio concorrente nel panorama dei linguaggi per i dati. Grazie alla facilità d’uso, alla velocità e al supporto per i casi d’uso di calcolo scientifico, imparare Julia è oggi una strada valida per molti data scientist.

Questo articolo offre un tutorial approfondito e corposo sul linguaggio Julia per principianti, che copre la sintassi di base, il lavoro con diversi tipi di dati, vettori e stringhe, data frame e molto altro. 

Perché imparare Julia? 

Ci sono molte ragioni alla base della popolarità di Julia. Anche se Python e R dominano come i linguaggi più usati nella data science, Julia è stato scaricato oltre 35 milioni di volte. Le ragioni della sua crescita sono spiegate in dettaglio in questo articolo, ma ecco un riepilogo delle caratteristiche e dei vantaggi di Julia: 

  • Velocità: Uno dei principi fondanti di Julia è essere generale come Python, adatto alle statistiche come R, ma veloce come C. Questo abilita workflow sui dati più rapidi, soprattutto per il calcolo scientifico.
  • Sintassi: Un altro punto di forza di Julia è la sintassi. Nonostante la velocità, la sintassi di Julia è dinamicamente tipizzata, quindi più vicina a R e Python che a linguaggi più complessi. Questo la rende molto più accessibile e facile da imparare rispetto ad altri linguaggi ad alte prestazioni.
  • Multiple dispatch: Senza entrare troppo nei tecnicismi, un altro vantaggio di Julia è il multiple dispatch, ovvero la capacità delle funzioni di comportarsi in modo diverso in base ai tipi degli argomenti passati. Questo la rende un linguaggio flessibile con cui lavorare. 

Grazie a queste caratteristiche, Julia è stata nella top 5 dei linguaggi più amati nel sondaggio per sviluppatori di StackOverflow 2022. Ciò vale in particolare per chi nella comunità scientifica cerca di passare da strumenti come Matlab.

stackoverflow 2022 developer survey

Screenshot dal sondaggio StackOverflow Developer 2022

Detto questo, vediamo nel dettaglio come iniziare con Julia. 

Inizia con Julia

Padroneggia le basi di Julia con il nostro corso introduttivo interattivo e pratico su Julia.

Inizia a Imparare

Come installare ed eseguire Julia? 

Installare Julia è semplice. Scarica l’installer specifico per il tuo sistema operativo da questa pagina del sito di Julia e seleziona la casella che aggiunge Julia al PATH di sistema. Se digiti julia nella riga di comando e ottieni il seguente output, è tutto a posto.

how to install julia.gif

Installazione ed esecuzione del linguaggio Julia

Sia PyCharm che VSCode offrono plugin per Julia che supportano evidenziazione del codice e debug. Tuttavia, dato che i data scientist di solito preferiscono lavorare in JupyterLab, è anche facile aggiungere un kernel Julia a JupyterLab. Dopo essere entrato nel prompt julia nel terminale, inserisci semplicemente queste due righe:

using Pkg
add("IJulia")

Il codice aggiunge il pacchetto IJulia all’installazione di Julia sul sistema. Riavvia eventuali sessioni JupyterLab in esecuzione e dovresti vedere l’opzione Julia nel Launcher.

Julia in jupyterlab

Julia in esecuzione in JupyterLab

Sintassi di base di Julia

Questa sezione copre i fondamenti della sintassi di Julia, incluse variabili, tipi di dati, operatori, vettori e così via.

Operatori  

In programmazione, esistono caratteri speciali chiamati operatori che rappresentano specifici processi matematici o logici. Di seguito trovi gli operatori più comuni, tra cui addizione, sottrazione, moltiplicazione, divisione e altro. 

Nota: In Julia, i commenti si scrivono con un singolo carattere #. Qualsiasi cosa scritta dopo # viene ignorata dal programma (fino a fine riga).

# Addition in Julia
37 + 73
110
# Subtraction in Julia
17 - 19
-2
# Multiplication in Julia
29 * 33
957
# Division in Julia
22 / 7
3.142857142857143
# Backwards division in Julia, the above is equal to 0 / 5.
5 \ 0
0
# The remainder operator in Julia 
98456 % 23
16
# Raising to the power 2 (squaring)
(22 / 7) ^ 2
9.877551020408163
# Raising to the power 3 (cubing)
-7 ^ 3
-343
# Raising to the power 0.5 (square-rooting)
(355 / 133) ^ 0.5
1.633760365638372

Variabili

In programmazione usiamo le variabili per memorizzare informazioni. Le variabili permettono di fare riferimento a un’informazione tramite un nome. Per esempio, qui stiamo memorizzando il numero 73 in una variabile chiamata a e la stringa “Hello world!” nella variabile “word” (vedremo i diversi tipi di dati di Julia nella sezione seguente). Digitare il nome della variabile nella riga di comando ne stampa il valore.

# Storing the number 73 in a variable a
a = 73
a
73
# Storing "Hello world!" in word
word = "Hello world!"
word
Hello world!

Tutti gli operatori e le funzioni di Julia funzionano allo stesso modo sulle variabili.

# Multiplying two numeric variables
a = 73
b = 37
a * b
2701

Puoi usare qualsiasi carattere alfanumerico nei nomi delle variabili, inclusi gli underscore. Tuttavia, in Julia ci sono alcune convenzioni per denominarle. I nomi delle variabili:

  • sono in lower_snake_case (minuscolo e parole separate da underscore)
  • non possono iniziare con un numero.

Introduzione ai tipi di dati in Julia 

I tipi di dati dicono al programma quali valori può assumere una variabile e quali operatori matematici o logici possono essere usati su di essa senza generare errori. In Julia esistono vari tipi di dati; qui sotto trovi i più basilari.

Tipo di dato

Espressione

Esempi

Descrizione

Interi

Int64

1, -99

Numeri interi

Float

Float64

1.23, 4.0

Numeri con la virgola decimale 

Booleani

Bool

true, false

Valori logici

Stringhe

String

"Hello", "A", ""

Dati testuali con un qualsiasi numero di caratteri

Caratteri

Char

'A', '$', '\u20AC'

Dati testuali con esattamente un carattere

Il tipo di dato di una variabile può essere stampato usando la funzione typeof():

# Getting the type of 3.14
typeof(3.14)
Float64

Quando esegui operazioni in Julia, devi tenere a mente le conseguenze dei diversi tipi di dati. Ad esempio, moltiplicare un float e un intero restituisce un float.

# Bank balance as an integer
balance = 100

# Interest rate as a float
interest_rate = 1.05

# Interest rate as a float
new_balance = balance * interest_rate

typeof(new_balance)
Float64

Al contrario, eseguire operazioni tra stringhe e tipi numerici porta spesso a un errore. Come nel seguente caso.

# Bank balance as an integer
balance = 100

# Interest rate as a string
interest_rate = “5%”

# Interest rate as a float
new_balance = balance * interest_rate
ERROR: MethodError:
no method matching *(::Int64, ::String)

Conversione dei tipi di dati in Julia 

È possibile convertire diversi tipi di dati in altri tipi in Julia con funzioni dedicate disponibili di default. Di seguito una tabella che mostra come convertire vari tipi di dati in Julia. 

Da

A

Funzione

Intero

Float

Float64(x)

Float

Intero

Int64(x)

Intero o float

Stringa

string(x)

Stringa

Float

parse(Float64, string)

Stringa

Intero

parse(Int64, string)

Lavorare con caratteri e stringhe in Julia 

Nella sezione precedente abbiamo visto i tipi di dato carattere e stringa in Julia; in questa sezione approfondiremo le operazioni comuni su caratteri e stringhe. Ricorda: una stringa è una serie ordinata di caratteri o semplice testo racchiuso tra doppi apici. 

# A string is a series of characters
word = "Hello world!"
word
"Hello world!"

Per scrivere una stringa su più righe, puoi usare i tripli apici:

multi_line = """
   This is a
   multi-line string.
"""
print(multi_line)
This is a
multi-line string.

Puoi estrarre informazioni e attributi dalle stringhe in vari modi. Ad esempio, l’indicizzazione con le parentesi quadre può estrarre singoli caratteri o un intervallo di caratteri in base alla posizione. In questo esempio, 1:3 indica la sequenza di numeri da 1 a 3 inclusi.

text = "Julia is a high-level, dynamic programming language"
# Extracting the first character in text based on its index
text[1]
'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
# Extracting the first three characters in text based on its index
text[1:3]
"Jul"

Qui sopra abbiamo messo 1 tra parentesi e ha restituito il primo carattere. In alternativa, puoi usare anche l’operatore speciale begin:

# Extracting the first character in text with the begin operator
text[begin]
'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)

Quando estrai un singolo carattere da una stringa, il suo tipo di dato sarà Char.

# Extract the type of the fourth character
typeof(text[4])
Char
# Extract the type of the fourth character
typeof(text[4])

Tuttavia, quando fai uno slicing di una stringa ed estrai più di un carattere, l’output è ancora una stringa. 

# Extract the type of the first 5 characters
typeof(text[begin:5])
String

Qui sopra stiamo estraendo i primi cinque caratteri di text. Tieni presente che entrambi gli indici ai lati dei due punti sono inclusivi. A differenza di Python, gli indici negativi non sono supportati. Invece, userai l’operatore end con un numero negativo. Ad esempio, per ottenere il terzo carattere dalla fine della stringa, puoi scrivere quanto segue. 

# Third from the end of the string
text[end-2]
'a': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)

Operazioni comuni sulle stringhe in Julia 

Puoi usare la funzione length() per contare il numero di caratteri in una stringa.

# Count the number of characters in a string
length(text)
51

Un’operazione comune è combinare due o più stringhe. Puoi farlo con la funzione string(), passando le stringhe separate da virgole.

# Creating one string from "Julia", "is", "awesome"
string("Julia ", "is ", "awesome!")
"Julia is awesome!"

Puoi anche usare l’operatore * (moltiplicazione) per unire stringhe

# Creating one string by multiplying strings
"73 " * "is " * "the " * "best " * "prime."
"73 is the best prime."

In entrambi i casi, ricordati di lasciare uno spazio tra le parole. Un altro modo per unire stringhe è ripetere una stringa più volte. Ecco come si fa con l’operatore ^ (esponenziazione):

# Creating one string with the exponentiation operator
"Three times the fun!" ^ 3
"Three times the fun! Three times the fun! Three times the fun!"

Array in Julia 

Un altro tipo di dato comune in Julia e in altri linguaggi sono gli array, come vengono chiamati in Julia. Gli array sono un elenco di valori racchiusi tra parentesi quadre []. Gli array vengono chiamati diversamente in base alle loro dimensioni. 

  • Gli array monodimensionali si chiamano vettori.
  • Gli array bidimensionali si chiamano matrici.
  • Gli array tridimensionali e oltre si chiamano semplicemente array.

In questo tutorial ci concentreremo esclusivamente sui vettori. I vettori possono essere creati passando gli elementi tra una coppia di parentesi quadre, come sotto.

# Define a three-integer vector
x = [1, 2, 3]
3-element Vector{Int64}:
1
2
3
# Get the type of x
typeof(x)
Vector{Int64} (alias for Array{Int64, 1})

Il vettore x ha tipo Vector, che è un alias per un array monodimensionale (come indicato dall’output alias for Array{In664, 1}). Tutti i suoi elementi hanno tipo di dato Int64. Ma non significa che tutti gli elementi debbano sempre avere lo stesso tipo. Ad esempio, ecco un vettore con elementi di tipi diversi. 

# Define a vector with various data types
array = [1, "Hello", 22/7]
3-element Vector{Any}:
1
"Hello"
3.142857142857143

Come puoi vedere, gli elementi del vettore hanno il tipo di dato Any tra parentesi graffe, che rappresenta tipi multipli. Puoi persino passare vettori dentro vettori.

# Define a vector with various data types, including another vector
array = [1, 2, 3, ["Hello", 73]]
4-element Array{Any,1}:
1
2
3
Any["Hello", 73]

La funzione length() funziona allo stesso modo sui vettori come sulle stringhe, poiché conta il numero di elementi all’interno dell’array.

# Get the length of a vector
length(array)
3

Le regole di indicizzazione funzionano in modo simile — qui stiamo indicizzando il primo e il secondo elemento dell’array.

# Get the first element of a vector
array = [1, "Hello", 22/7]
array[begin]
1
# Get the last element of a vector
array[end-1]
"Hello"

Operatori booleani e confronti numerici 

I linguaggi di programmazione usano i valori booleani per implementare la logica. Un booleano può avere solo due valori: true o false. I booleani sono particolarmente utili quando si lavora con confronti numerici e operatori booleani. Vediamo prima i confronti numerici.

Operatori di confronto numerico in Julia

Nella tabella seguente trovi gli operatori di confronto numerico più utilizzati in Julia. Come vedrai negli esempi, puoi confrontare booleani, oggetti numerici e altro usando gli operatori di confronto numerico.  Nota che puoi usare uno o due simboli per scrivere minore/maggiore o uguale. Per usare la versione a un simbolo, digita \le quindi TAB o \ge quindi TAB.

Operatore

Nome

Esempio

==

uguaglianza

1 == 3 # Restituisce false

!=

diseguaglianza

1 != 3 # Restituisce true

<

minore di

1 < 3 # Restituisce true

<=, ≤

minore o uguale a

length("Hello") <= 5 # Restituisce true

>

maggiore di

1 > 1 # Restituisce false

>=, ≥

maggiore o uguale a

true >= true # Restituisce true

Operatori booleani in Julia

Oltre agli operatori di confronto numerico, ci sono gli operatori booleani — come and/or. Possono essere usati per combinare due o più espressioni booleane in una sola. Qui sotto trovi gli operatori booleani più comuni e come si usano. 

Operatore

Nome

Esempi

&&

operatore and

true && true # Restituisce true


5 > 4 && 3^3 == 27 # Restituisce true

||

operatore or

73 > 45 || 73 % 2 == 0 # Restituisce true

Sintassi intermedia di Julia

Istruzioni condizionali in Julia

Usando valori e operatori booleani, puoi scrivere istruzioni condizionali. Ad esempio, se qualcosa è vero — esegui un’operazione. Se è falso, eseguirne un’altra. Le istruzioni condizionali sono dette anche if/else. Ecco come si scrivono:

if true
   print("Do something")
else
   print("Do something else")
end

Ogni volta che una condizione dopo la parola chiave if risulta true, viene eseguito il blocco if, altrimenti viene eseguito il blocco else. Questo esempio mostra una condizione che verifica se un numero è pari o dispari.

number = 5

if number % 2 == 0
   print("The number is even")
else
   print("The number is odd")
end
The number is odd

Nell’espressione dopo if, usiamo l’operatore modulo, %, per verificare se il numero restituisce 0 come resto quando diviso per 2. Risulta false perché il resto di 5 diviso 2 è 1. Pertanto, viene eseguita la clausola else. Ora, usiamo la nostra conoscenza degli operatori booleani e delle istruzioni condizionali per scrivere un programma che controlla se un numero è positivo, negativo o zero. 

number = 0

if number > 0
   print("The number is positive.")
elseif number == 0
   print("The number is equal to zero.")
else
   print("The number is negative.")

end
The number is equal to zero.

Aggiungiamo la nuova condizione usando la clausola elseif. Si usano le clausole elseif quando dobbiamo verificare due o più condizioni in un ordine preciso. 

For loop 

Un for loop in programmazione viene usato per iterare ed eseguire operazioni su ciascun elemento di una sequenza (stringa, vettore, tupla, insieme, ecc.) in ordine. La sintassi del for loop è lineare in Julia. Ad esempio, costruiamo un loop che stampa ciascun elemento di un vettore:

languages = ["Julia", "Python", "R"]

for lang = languages
   println(lang)

end
Julia
Python
R

Il for loop inizia scrivendo la parola chiave for. Poi scegliamo un nome di variabile temporanea. In questo caso scegliamo lang, il cui valore cambia man mano che percorriamo il vettore languages. Quindi, per specificare che stiamo iterando su languages, scriviamo il suo nome dopo l’operatore di assegnazione =.

All’interno del loop, usiamo la funzione println() per stampare ciascun elemento di languages, rappresentato dalla variabile temporanea lang. lang è detta variabile temporanea perché non è accessibile una volta che il loop termina.

Nota: La differenza tra print() e println() è che println() stampa gli elementi su nuove righe.

Puoi usare tutto ciò che hai imparato finora all’interno dei for loop. Ad esempio, creiamo un array di 10 interi e stampiamo solo i numeri dispari:

integers_vector = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for i = integers_vector
   # Print only odd numbers
   if i % 2 == 1
       println(i)
   end
end
1
3
5
7
9

Non dimenticare di chiudere sia il for loop che l’istruzione if con end.

Puoi iterare anche sulle stringhe, poiché sono anch’esse sequenze. Qui, ad esempio, stampiamo ciascuna lettera della stringa "Julia".

for letter = "Julia"
   println(letter)

end
J
u
l
i
a

Installare pacchetti in Julia 

Tutti gli operatori (+, -, *, ^), le funzioni (print(), println(), typeof()) e le parole chiave (for, if, else) che abbiamo usato sono inclusi nell’installazione di Julia. Tuttavia, il codice dell’installazione non basta per risolvere tutti i nostri problemi. Dobbiamo installare pacchetti per caricare strumenti e funzioni con funzionalità aggiuntive. I pacchetti sono librerie di codice pre-scritto (da altri programmatori) che possiamo aggiungere alla nostra installazione di Julia e che ci aiutano a risolvere problemi specifici.

Ad esempio, installeremo due pacchetti chiamati DataFrames e CSV che ci aiutano a manipolare informazioni in file CSV. Ecco come fare:

using Pkg
Pkg.add("DataFrames")
Pkg.add("CSV")

La classe Pkg è il gestore pacchetti globale di Julia. La sua funzione add() aggiunge nuovi pacchetti all’installazione. Per importare tutti i nomi di funzione dalle librerie, le importiamo con la parola chiave using all’inizio dei nostri script.

using DataFrames
using CSV

Useremo le loro funzioni nella prossima sezione.

Primi passi con i DataFrame 

Un DataFrame è un tipo di dato speciale nei linguaggi per la data science come Julia, Python o R, progettato per lavorare con tabelle. Il formato di file più popolare per memorizzare tabelle è il CSV (comma-separated values). La libreria DataFrame contiene gli strumenti per caricare tali file e manipolare le informazioni al loro interno.

Importare file CSV

Importeremo il dataset diamonds per questo tutorial. Il file diamonds.csv si presenta così:

diamonds dataset.png

Accedi qui al dataset diamonds

La prima riga è chiamata intestazione e contiene i nomi delle colonne. Carichiamolo usando i pacchetti CSV e DataFrames.

# Load in the diamonds DataFrame
diamonds = DataFrame(CSV.File("data/diamonds.csv"))
typeof(diamonds)
DataFrame

Passiamo il percorso del file CSV a CSV.File(), che a sua volta viene passato alla funzione DataFrame(). L’output di typeof() mostra che l’operazione è andata a buon fine.

Semplici manipolazioni dei dati 

La prima cosa da fare quando hai un nuovo DataFrame è stamparne alcune righe per vedere com’è fatto. Puoi farlo con la funzione first().

# Print the first 7 rows of diamond
first(diamonds, 7)
7 × 10 DataFrame

first 7 rows.png

Prime 7 righe del dataset diamonds

La funzione first() restituisce le prime n righe di un DataFrame. Come possiamo vedere, ci sono 6 colonne numeriche e 3 categoriche (testuali). L’indice è contato anch’esso come colonna nei DataFrame di Julia. 

C’è anche la funzione last() che funziona in modo simile a first(). Dal nome puoi già intuire cosa fa. Esatto, prende le ultime n righe.

Un’altra funzione utile è size(). Stampa il numero di righe e colonne di un DataFrame.

# Print the dimensions of diamonds
size(diamonds)
(10000, 10)

Il nostro ha 10 mila righe e 10 colonne (incluso l’indice). L’occhio umano non può gestire tutte le 10k righe, quindi usiamo statistiche descrittive per farci un’idea della distribuzione delle colonne nel dataset. Per farlo, usiamo la funzione describe()

# Summary statistics of diamonds using describe()
describe(diamonds)
10 × 7 DataFrame

describe output.png

Output della funzione describe()

L’output è una tabella riepilogativa con 7 colonne. Le colonne min, max, mean e median si applicano solo alle colonne numeriche. Nella colonna eltype puoi vedere i tipi di dato di ciascuna colonna in diamonds. I numeri accanto a String si riferiscono alla lunghezza della stringa più lunga in quella colonna.

Puoi anche usare la seguente sintassi per selezionare singole colonne dal DataFrame.

# Extracting the first 10 rows of the cut column
diamonds[:, :cut][begin:10]
10-element PooledArrays.PooledArray{String15,UInt32,1,Array{UInt32,1}}:

"Ideal"
"Ideal"
"Ideal"
"Premium"
"Fair"
"Premium"
"Very Good"
"Ideal"
"Ideal"
"Premium"

Il primo due punti tra parentesi seleziona tutte le righe. Poi specifichiamo il nome della colonna dopo una virgola e un altro due punti (senza virgolette). Quindi usiamo l’indicizzazione sull’array restituito per selezionare le prime dieci righe. Per rendere il codice sopra ancora più semplice, potremmo selezionare le prime dieci righe direttamente tra le prime parentesi come segue:

# Extracting the first 10 rows of the cut column
diamonds[1:10, :carat]
10-element PooledArrays.PooledArray{String15,UInt32,1,Array{UInt32,1}}:

"Ideal"
"Ideal"
"Ideal"
"Premium"
"Fair"
"Premium"
"Very Good"
"Ideal"
"Ideal"
"Premium"

Per selezionare più colonne, inserisci i nomi delle colonne (con i due punti) tra parentesi.

# Extracting the first 5 rows of the cut, carat, price columns
diamonds[1:5, [:carat, :cut, :price]]
5 × 3 DataFrame

image2.png

Le prime 5 righe e 3 colonne del dataset diamonds

Eseguiamo la funzione describe() solo sulle colonne numeriche

# Running describe on numeric columns only
describe(diamonds[:, [:carat, :depth, :table, :price, :x, :y]])

describe_numeric.png

Esecuzione di describe() solo sulle colonne numeriche

Per vedere il DataFrame ordinato in ordine crescente in base a una colonna, usa la funzione sort():

# Sort diamonds and see the first 5 rows
sorted_diamonds = sort(diamonds, :price)
first(sorted_diamonds, 5)

sorted.png

La tabella riepilogativa ordinata

Per ordinare in ordine decrescente, avvolgi la funzione order() attorno al nome della colonna.

# Sort diamonds in descending order and see the first 5 rows
sorted_diamonds = sort(diamonds, order(:price, rev=true))
first(sorted_diamonds, 5)

sorted descending.png

La tabella riepilogativa ordinata, in ordine decrescente

Fantastico! Ora puoi caricare DataFrame ed eseguire un’esplorazione di base dei dati.

Per saperne di più su Julia 

Nonostante sia un linguaggio giovane, esiste già una grande quantità di risorse per padroneggiare Julia. Eccone alcune per migliorare le tue competenze:

Inizia con Julia

Padroneggia le basi di Julia con il nostro corso introduttivo interattivo e pratico su Julia.

Argomenti
Julia
Data Science
Correlato

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

Mostra AltroMostra Altro