Il design linguistico riguarda il bilanciamento di diversi tipi di complessità. Python generalmente sceglie una sintassi semplice, a scapito della semantica più complessa. Una di queste scelte è che esiste un solo modo per definire un callable (funzione, metodo di istanza, metodo di classe, metodo statico): con def
. (Tecnicamente, anche con lambda
, ma lo ignorerò qui).
Invece di introdurre parole chiave extra come static
o method
, Python disambigura questi casi usando il protocollo descrittore . Un descrittore è un oggetto con un metodo __get__
(e possibilmente anche __set__
e __del__
) e può essere usato per controllare come è possibile accedere ai membri della classe. Ogni def
crea un oggetto funzione, che soddisfa anche il protocollo descrittore.
Quando la nostra funzione viene assegnata direttamente a un oggetto, il protocollo descrittore non viene richiamato quando si accede a quel campo. Si comporta come una funzione assegnata.
def f(a, b): # no "self"
return a + b
class C(object): pass
o = C()
c.f = f # assign as instance field
o.f(2, 3) #=> 5
Quando assegniamo una funzione a una classe, accedendola si attiverà il protocollo descrittore. Per le funzioni predefinite, questo restituirà la funzione stessa quando si accede alla classe. Quando si accede tramite un'istanza di quella classe, restituirà un oggetto metodo che ha associato l'istanza su cui è stato richiamato al primo argomento della funzione.
def f(self, a, b):
return a + b
class C(object): pass
o = C()
C.f = f # assign as class member
C.f is f #=> True, the class member is returned unchanged
o.f is f #=> False, this is a bound method
# in the absence of subclasses, these calls are the same:
o.f(2, 3) #=> 5
C.f(o, 2, 3) #=> 5
f(o, 2, 3) #=> 5
instance_method = o.f
instance_method(2, 3) #=> 5
Il% decoratore di% co_de avvolge la funzione con un descrittore differente.
Quando si accede tramite la classe o tramite un'istanza di quella classe, il primo argomento è associato alla classe:
def f(cls, a, b):
return a + b
class C(object): pass
o = C()
C.f = classmethod(f) # assign as class member
C.f is f #=> False, we get a bound method
o.f is f #=> False, also bound to the class
# in the absence of subclasses, these calls are the same:
C.f(2, 3) #=> 5
f(C, 2, 3) #=> 5
o.f(2, 3) #=> 5
Il classmethod
decorator restituisce semplicemente la funzione in entrambi i casi, senza vincolare alcun argomento.
def f(a, b):
return a + b
class C(object): pass
o = C()
C.f = staticmethod(f) # assign as class member
C.f is f #=> True
o.f is f #=> True
# in the absence of subclasses, these calls are the same:
C.f(2, 3)
o.f(2, 3)
f(2, 3)
I descrittori vengono anche utilizzati per implementare le proprietà. Qui, l'accesso a una proprietà tramite un'istanza restituisce il risultato del richiamo della funzione wrapped come metodo:
def f(self):
return 42
p = property(f)
class C(object): pass
o = C()
C.p = p
C.p is p #=> True
o.p is p #=> False, this is the result of the function
# these calls are equivalent:
f(o) #=> 42
o.p #=> 42
Quindi questa combinazione di caratteristiche "esplicito staticmethod
argomento" - "descrittori" - "decoratori" offre al linguaggio molta flessibilità. Le parti più complesse sono nascoste nel protocollo descrittore, di cui gli utenti ordinari non devono preoccuparsi. Se ci fosse stata una parola chiave self
implicita, l'implementazione dei metodi di classe sarebbe stata più difficile e più confusa per gli utenti. Dopotutto, self
è inteso come riferito a un'istanza della classe, e self
è preferito invece quando si riferisce alla classe stessa.
Naturalmente questo non è l'unico modo per farlo. JavaScript è un linguaggio largamente simile che ha una percentuale implicita di% co_de. Ogni volta che si accede a una funzione come membro dell'oggetto, il parametro cls
della funzione è associato a quell'oggetto. Cioè dato un oggetto o che ha un membro funzione this
, this
restituisce una funzione con un f
associato a o.f
. Questo è equivalente a Python, dove this
e o
possono essere usati in modo intercambiabile perché i metodi di istanza sono legati al loro oggetto.
Tuttavia, JavaScript non ha un concetto paragonabile di classi e non ha modo di recuperare un metodo non associato da un oggetto. Inoltre non ha sovraccarico dell'operatore, quindi non è possibile avere diversi tipi di funzioni che rappresentano associazioni diverse. Invece, ogni funzione ha metodi o.f()
e m = o.f; m()
. Differiscono dal chiamare direttamente la funzione f.call()
in quanto devi fornire esplicitamente un valore per f.apply()
.
L'approccio JavaScript è generalmente intuitivo, ma rende difficile memorizzare una funzione da qualche parte senza modificare accidentalmente il contesto f()
. E poiché this
è un parametro largamente implicito, è difficile tracciare a quale oggetto% co_de farà riferimento, specialmente quando si ha a che fare con funzioni annidate. Python non soffre di questi problemi, o almeno in misura molto minore. C'è una differenza concettuale più strong tra funzioni e metodi associati rispetto a JS. Avendo un parametro this
esplicito, il flusso di dati nei tuoi metodi è molto più chiaro.
Il this
di Python utilizza l'introspezione stack-trace per ottenere il primo argomento della funzione di inclusione non è necessariamente un ottimo progetto: è magico. La magia è cattiva, la magia può sbagliare in modi imprevisti e la magia è difficile da eseguire il debug. Tuttavia, nella maggior parte dei casi è molto più semplice che dover fornire tutti i parametri esplicitamente. Ciò a sua volta riduce la possibilità di errori e incoraggia i programmatori a scrivere codice semplice e corretto. Quindi, anche se non penso che questa magia sia ottima, è comunque buona nel complesso.