Qualcuno potrebbe spiegarmi il significato di @classmethod
e @staticmethod
in python? Ho bisogno di sapere la differenza e il significato.
Per quanto ho capito, @classmethod
dice ad una classe che è un metodo che dovrebbe essere ereditato nelle sottoclassi, o... qualcosa. Tuttavia, che senso ha? Perché non definire semplicemente il metodo di classe senza aggiungere @classmethod
o @staticmethod
o qualsiasi definizione @@
?
tl;dr: quando dovrei usarli, perché dovrei usarli e come dovrei usarli?
Sono abbastanza avanzato con il C++, quindi usare concetti di programmazione più avanzati non dovrebbe essere un problema. Sentitevi liberi di darmi un esempio corrispondente in C++, se possibile.
Sebbene classmethod
e staticmethod
siano abbastanza simili, c'è una leggera differenza nell'uso di entrambe le entità: classmethod
deve avere un riferimento ad un oggetto di classe come primo parametro, mentre staticmethod
può non avere alcun parametro.
class Date(object):
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
@classmethod
def from_string(cls, date_as_string):
day, month, year = map(int, date_as_string.split('-'))
date1 = cls(day, month, year)
return date1
@staticmethod
def is_date_valid(date_as_string):
day, month, year = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999
date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')
Supponiamo un esempio di una classe che si occupa di informazioni sulle date (questo sarà il nostro boilerplate):
class Date(object):
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
Questa classe ovviamente potrebbe essere usata per memorizzare informazioni su certe date (senza informazioni sul fuso orario; assumiamo che tutte le date siano presentate in UTC).
Qui abbiamo __init__
, un tipico inizializzatore di istanze di classi Python, che riceve argomenti come un tipico instancemethod
, avendo il primo argomento non opzionale (self
) che contiene un riferimento ad un'istanza appena creata.
Metodo della classe
Abbiamo alcuni compiti che possono essere svolti piacevolmente usando i `metodi di classe'.
*Supponiamo di voler creare un sacco di istanze della classe Date
con informazioni sulla data provenienti da una fonte esterna codificata come una stringa con formato 'dd-mm-yyyy'. Supponiamo di doverlo fare in diversi punti del codice sorgente del nostro progetto.
Quindi quello che dobbiamo fare qui è:
Date
passando questi valori alla chiamata di inizializzazione.Questo assomiglierà a:
day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)
Per questo scopo, il C++ può implementare tale funzione con l'overloading, ma Python manca di questo overloading. Invece, possiamo usare classmethod
. Creiamo un altro "costruttore".
@classmethod
def from_string(cls, date_as_string):
day, month, year = map(int, date_as_string.split('-'))
date1 = cls(day, month, year)
return date1
date2 = Date.from_string('11-09-2012')
Guardiamo più attentamente l'implementazione di cui sopra, e rivediamo quali vantaggi abbiamo qui:
cls
è un oggetto che contiene la classe stessa, non un'istanza della classe. È abbastanza figo perché se ereditiamo la nostra classe Date
, tutti i figli avranno anche definito from_string
.Metodo statico
Che dire di staticmethod
? E' abbastanza simile al classmethod
ma non prende nessun parametro obbligatorio (come fa un metodo di classe o di istanza).
Vediamo il prossimo caso d'uso.
*Abbiamo una stringa di date che vogliamo validare in qualche modo. Anche questo compito è logicamente legato alla classe Date
che abbiamo usato finora, ma non richiede l'istanza di essa.
Qui è dove il staticmethod
può essere utile. Guardiamo il prossimo pezzo di codice:
@staticmethod
def is_date_valid(date_as_string):
day, month, year = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999
# usage:
is_date = Date.is_date_valid('11-09-2012')
Quindi, come possiamo vedere dall'uso di staticmethod
, non abbiamo alcun accesso a ciò che è la classe - è fondamentalmente solo una funzione, chiamata sintatticamente come un metodo, ma senza accesso all'oggetto e ai suoi interni (campi e altri metodi), mentre classmethod sì.
La risposta di Rostyslav Dzinko è molto appropriata. Ho pensato di evidenziare un'altra ragione per cui dovreste scegliere @classmethod
invece di @staticmethod
quando state creando un costruttore aggiuntivo.
Nell'esempio sopra, Rostyslav ha usato il @classmethod
from_string
come costruttore per creare oggetti Date
da parametri altrimenti inaccettabili. Lo stesso può essere fatto con @staticmethod
come mostrato nel codice qui sotto:
-- language-all: python -->
class Date:
def __init__(self, month, day, year):
self.month = month
self.day = day
self.year = year
def display(self):
return "{0}-{1}-{2}".format(self.month, self.day, self.year)
@staticmethod
def millenium(month, day):
return Date(month, day, 2000)
new_year = Date(1, 1, 2013) # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object.
# Proof:
new_year.display() # "1-1-2013"
millenium_new_year.display() # "1-1-2000"
isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True
Quindi sia new_year
che millenium_new_year
sono istanze della classe Date
.
Ma, se osservate da vicino, il processo Factory è hard-coded per creare oggetti Date
, non importa cosa. Ciò significa che anche se la classe Date
viene sottoclassificata, le sottoclassi creeranno comunque semplici oggetti Date
(senza alcuna proprietà della sottoclasse). Vedi questo nell'esempio qui sotto:
class DateTime(Date):
def display(self):
return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)
datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)
isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False
datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class for more details.
datetime2
non è un'istanza di DateTime
? WTF? Beh, questo è dovuto al decoratore @staticmethod
utilizzato.
Nella maggior parte dei casi, questo non è desiderato. Se quello che volete è un metodo Factory che sia consapevole della classe che lo ha chiamato, allora @classmethod
è quello che vi serve.
Riscrivendo Date.millenium
come (questa è l'unica parte del codice precedente che cambia):
@classmethod
def millenium(cls, month, day):
return cls(month, day, 2000)
assicura che la classe
non è hard-coded ma piuttosto imparata. cls
può essere qualsiasi sottoclasse. L'oggetto risultante sarà giustamente un'istanza di cls
.
Facciamo una prova:
datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)
isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True
datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"
Il motivo è, come ormai sapete, che @classmethod
è stato usato al posto di @staticmethod
.
@classmethod
significa: quando questo metodo viene chiamato, passiamo la classe come primo argomento invece dell'istanza di quella classe (come facciamo normalmente con i metodi). Questo significa che potete usare la classe e le sue proprietà all'interno di quel metodo piuttosto che una particolare istanza.
@staticmethod
significa: quando questo metodo viene chiamato, non gli passiamo un'istanza della classe (come facciamo normalmente con i metodi). Questo significa che puoi mettere una funzione dentro una classe ma non puoi accedere all'istanza di quella classe (questo è utile quando il tuo metodo non usa l'istanza).