Perché hai bisogno esplicitamente di avere l'argomento "self" in un metodo Python?


197

Quando si definisce un metodo su una classe in Python, è simile al seguente:

class MyClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

Ma in alcuni altri linguaggi, come C #, hai un riferimento all'oggetto a cui il metodo è associato con la parola chiave "this" senza dichiararlo come argomento nel prototipo del metodo.

È stata una decisione intenzionale di progettazione del linguaggio in Python o ci sono alcuni dettagli di implementazione che richiedono il passaggio di "sé" come argomento?


15
Scommetto che ti interesserebbe anche sapere perché devi scrivere esplicitamente selfper accedere ai membri - stackoverflow.com/questions/910020/…
Piotr Dobrogost

1
Ma sembra un po 'una piastra della caldaia
Raghuveer,

Un po 'confuso ma vale la pena capire stackoverflow.com/a/31367197/1815624
CrandellWS

Risposte:


91

Mi piace citare lo Zen di Python di Peters. "Esplicito è meglio che implicito."

In Java e C ++, ' this.' può essere dedotto, tranne quando si hanno nomi di variabili che lo rendono impossibile da dedurre. Quindi a volte ne hai bisogno ea volte no.

Python sceglie di rendere esplicite cose del genere piuttosto che basate su una regola.

Inoltre, poiché nulla è implicito o assunto, parti dell'implementazione sono esposte. self.__class__, self.__dict__E altre strutture "interni" sono disponibili in modo evidente.


53
Anche se sarebbe bello avere un messaggio di errore meno criptico quando lo dimentichi.
Martin Beckett,

9
Tuttavia quando si chiama un metodo non è necessario passare la variabile oggetto, non infrange la regola di esplicitazione? Se mantenere questo zen, deve essere qualcosa di simile a: object.method (object, param1, param2). Sembra in qualche modo incoerente ...
Vedmant,

10
"esplicito è meglio che implicito" - Lo "stile" di Python non è forse implicito? ad es. tipi di dati impliciti, limiti delle funzioni implicite (no {}), ambito delle variabili implicite ... se le variabili globali nei moduli sono disponibili nelle funzioni ... perché la stessa logica / ragionamento non dovrebbe essere applicato alle classi? La regola semplificata non sarebbe semplicemente "tutto ciò che è dichiarato a un livello superiore è disponibile a un livello inferiore" come determinato dal rientro?
Simon,

13
"Esplicito è meglio che implicito" rilevato Sciocchezze
Vahid Amiri,

10
ammettiamolo, è solo male. Non ci sono scuse per questo. È solo una brutta reliquia ma va bene.
Toskan,

63

È per minimizzare la differenza tra metodi e funzioni. Consente di generare facilmente metodi in metaclasse o di aggiungere metodi in fase di esecuzione a classi preesistenti.

per esempio

>>> class C(object):
...     def foo(self):
...         print "Hi!"
...
>>>
>>> def bar(self):
...     print "Bork bork bork!"
...
>>>
>>> c = C()
>>> C.bar = bar
>>> c.bar()
Bork bork bork!
>>> c.foo()
Hi!
>>>

Inoltre (per quanto ne so) semplifica l'implementazione del runtime di Python.


10
+1 per È per minimizzare la differenza tra metodi e funzioni. Questa dovrebbe essere una risposta accettata
utente

Questo anche alla radice della spiegazione molto collegata di Guido.
Marcin,

1
Ciò mostra anche che in Python, quando si fa prima c.bar (), controlla l'istanza per gli attributi, quindi controlla gli attributi di classe . Quindi puoi "collegare" un dato o una funzione (oggetti) a una Classe in qualsiasi momento e aspettarti di accedere alla sua istanza (es. Dir (istanza) sarà come). Non solo quando hai "creato" un'istanza c. È molto dinamico.
Nishant,

10
Non lo compro davvero. Anche nei casi in cui è necessaria la classe genitore, è comunque possibile dedurla al momento dell'esecuzione. E l'equivalenza tra i metodi di istanza e le funzioni di classe passate alle istanze è sciocca; Ruby sta bene senza di loro.
Zachaysan,

2
JavaScript consente di aggiungere metodi a un oggetto in fase di esecuzione e non è necessario selfnella dichiarazione di funzione (attenzione, forse questo sta lanciando pietre da una casa di vetro, poiché JavaScript ha una thissemantica di associazione piuttosto complicata )
Jonathan Benn

55

Suggerisco di leggere il blog di Guido van Rossum su questo argomento - Perché il sé esplicito deve rimanere .

Quando viene decorata una definizione di metodo, non sappiamo se assegnargli automaticamente un parametro 'self' oppure no: il decoratore potrebbe trasformare la funzione in un metodo statico (che non ha 'self') o un metodo di classe (che ha un tipo di sé divertente che si riferisce a una classe anziché a un'istanza), oppure potrebbe fare qualcosa di completamente diverso (è banale scrivere un decoratore che implementa '@classmethod' o '@staticmethod' in puro Python). Non c'è modo senza sapere cosa fa il decoratore se dotare il metodo di essere definito con un argomento implicito "sé" o no.

Respingo gli hack come "@classmethod" e "@staticmethod".


16

Python non ti costringe a usare "self". Puoi dargli qualunque nome tu voglia. Devi solo ricordare che il primo argomento in un'intestazione di definizione del metodo è un riferimento all'oggetto.


Per convenzione, dovrebbe comunque essere "self" per istanze o "cls" in cui sono coinvolti tipi (mmmm metaclassi)
pobk,

1
Costringe a mettere sé come primo parametro in ogni metodo, solo un testo extra che non ha molto senso per me. Altre lingue funzionano bene con questo.
Vedmant,

ho ragione ? sempre il primo parametro è un riferimento all'oggetto.
Mohammad Mahdi KouchakYazdi,

@MMKY No, ad esempio con @staticmethodnon lo è.
Segna il

1
"Devi solo ricordare che il primo argomento nella definizione di un metodo ..." Ho sperimentato di cambiare la parola "sé" in "kwyjibo" e ha ancora funzionato. Quindi, come viene spesso spiegato, non è importante la parola "sé", ma la posizione di qualunque cosa occupi quello spazio (?)
RBV

7

Ti permette anche di fare questo: (in breve, invocare Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)restituirà 12, ma lo farà nel modo più folle.

class Outer(object):
    def __init__(self, outer_num):
        self.outer_num = outer_num

    def create_inner_class(outer_self, inner_arg):
        class Inner(object):
            inner_arg = inner_arg
            def weird_sum_with_closure_scope(inner_self, num)
                return num + outer_self.outer_num + inner_arg
        return Inner

Ovviamente, questo è più difficile da immaginare in linguaggi come Java e C #. Rendendo esplicito il riferimento personale, sei libero di fare riferimento a qualsiasi oggetto con quel riferimento automatico. Inoltre, un tale modo di giocare con le classi in fase di esecuzione è più difficile da fare nei linguaggi più statici - non è necessariamente buono o cattivo. È solo che il sé esplicito permette a tutta questa follia di esistere.

Inoltre, immagina questo: vorremmo personalizzare il comportamento dei metodi (per la profilazione o qualche pazza magia nera). Questo può portarci a pensare: e se avessimo una classe il Methodcui comportamento potremmo ignorare o controllare?

Bene eccolo qui:

from functools import partial

class MagicMethod(object):
    """Does black magic when called"""
    def __get__(self, obj, obj_type):
        # This binds the <other> class instance to the <innocent_self> parameter
        # of the method MagicMethod.invoke
        return partial(self.invoke, obj)


    def invoke(magic_self, innocent_self, *args, **kwargs):
        # do black magic here
        ...
        print magic_self, innocent_self, args, kwargs

class InnocentClass(object):
    magic_method = MagicMethod()

E ora: InnocentClass().magic_method()agirà come previsto. Il metodo verrà associato con il innocent_selfparametro to InnocentClasse con l' magic_selfistanza MagicMethod. Strano eh? È come avere 2 parole chiave this1e this2in lingue come Java e C #. Magia come questa consente ai framework di fare cose che altrimenti sarebbero molto più dettagliate.

Ancora una volta, non voglio commentare l'etica di questa roba. Volevo solo mostrare cose che sarebbero più difficili da fare senza un esplicito riferimento di sé.


4
Quando considero il tuo primo esempio, posso fare lo stesso in Java: la classe interna deve chiamare OuterClass.thisper ottenere il 'sé' dalla classe esterna, ma puoi comunque usare thiscome riferimento a se stesso; molto simile a quello che fai qui in Python. Per me non è stato più difficile immaginarlo. Forse dipende dalla propria competenza nella lingua in questione?
klaar,

Ma puoi ancora fare riferimento a qualche ambito quando ti trovi all'interno di un metodo di una classe anonima, che è definito all'interno di una classe anonima, che è definita all'interno di un'implementazione anonima dell'interfaccia Something, che a sua volta è definita all'interno di un'altra implementazione anonima di Something? In Python puoi ovviamente fare riferimento a qualsiasi ambito.
vlad-ardelean,

Hai ragione, in Java puoi fare riferimento alla classe esterna solo chiamando il suo nome di classe esplicito e usandolo come prefisso this. I riferimenti impliciti sono impossibru in Java.
klaar,

Mi chiedo se questo funzionerebbe: in ogni ambito (ogni metodo) ha una variabile locale che fa riferimento al thisrisultato. Ad esempio Object self1 = this;(utilizzare Object o qualcosa di meno generico). Quindi, se si ha accesso alla variabile negli ambiti più elevati, si potrebbe avere accesso a self1, self2, ... selfn. Penso che questi dovrebbero essere dichiarati finali o qualcosa del genere, ma potrebbe funzionare.
vlad-ardelean

3

Penso che la vera ragione oltre a "The Zen of Python" sia che le funzioni sono cittadini di prima classe in Python.

Il che essenzialmente li rende un oggetto. Ora il problema fondamentale è se le tue funzioni sono anche oggetti, nel paradigma orientato agli oggetti come invieresti messaggi agli oggetti quando i messaggi stessi sono oggetti?

Sembra un problema con le uova di gallina, per ridurre questo paradosso, l'unico modo possibile è passare un contesto di esecuzione ai metodi o rilevarlo. Ma dal momento che Python può avere funzioni nidificate, sarebbe impossibile farlo poiché il contesto dell'esecuzione cambierebbe per le funzioni interne.

Questo significa che l'unica soluzione possibile è passare esplicitamente 'sé' (Il contesto dell'esecuzione).

Quindi credo che sia un problema di implementazione lo Zen è arrivato molto più tardi.


Ciao, sono nuovo di Python (dallo sfondo Java) e non ho capito bene cosa hai detto "come invieresti messaggi agli Oggetti quando i messaggi stessi sono oggetti". Perché è un problema, puoi approfondire?
Qiulang,

1
@Qiulang Aah, in Programmazione orientata agli oggetti i metodi di chiamata sugli oggetti è equivalente all'invio di messaggi agli oggetti con o senza un payload (parametri della funzione). I metodi internamente sarebbero rappresentati come un blocco di codice associato a una classe / oggetto e utilizza l'ambiente implicito disponibile tramite l'oggetto su cui viene invocato. Ma se i tuoi metodi sono oggetti, possono esistere indipendentemente dall'essere associati a una classe / oggetto che pone la domanda se invochi questo metodo in quale ambiente verrebbe eseguito?
pankajdoharey,

Quindi ci deve essere un meccanismo per fornire un ambiente, sé significherebbe l'ambiente attuale nel punto di esecuzione ma si potrebbe anche fornire un altro ambiente.
pankajdoharey,

2

Penso che abbia a che fare con PEP 227:

I nomi nell'ambito della classe non sono accessibili. I nomi vengono risolti nell'ambito della funzione di chiusura più interna. Se una definizione di classe si verifica in una catena di ambiti nidificati, il processo di risoluzione salta le definizioni di classe. Questa regola impedisce interazioni dispari tra gli attributi di classe e l'accesso alle variabili locali. Se si verifica un'operazione di associazione dei nomi in una definizione di classe, crea un attributo sull'oggetto classe risultante. Per accedere a questa variabile in un metodo o in una funzione nidificata all'interno di un metodo, è necessario utilizzare un riferimento all'attributo, tramite sé o tramite il nome della classe.


1

Come spiegato in sé in Python, Demysified

qualcosa come obj.meth (args) diventa Class.meth (obj, args). Il processo di chiamata è automatico mentre il processo di ricezione non lo è (è esplicito). Questo è il motivo per cui il primo parametro di una funzione in classe deve essere l'oggetto stesso.

class Point(object):
    def __init__(self,x = 0,y = 0):
        self.x = x
        self.y = y

    def distance(self):
        """Find distance from origin"""
        return (self.x**2 + self.y**2) ** 0.5

invocazioni:

>>> p1 = Point(6,8)
>>> p1.distance()
10.0

dentro () definisce tre parametri ma ne abbiamo appena passati due (6 e 8). Allo stesso modo distance () richiede uno ma sono stati passati zero argomenti.

Perché Python non si lamenta di questa mancata corrispondenza del numero di argomenti ?

Generalmente, quando chiamiamo un metodo con alcuni argomenti, la funzione di classe corrispondente viene chiamata posizionando l'oggetto del metodo prima del primo argomento. Quindi, qualsiasi cosa come obj.meth (args) diventa Class.meth (obj, args). Il processo di chiamata è automatico mentre il processo di ricezione non lo è (è esplicito).

Questo è il motivo per cui il primo parametro di una funzione in classe deve essere l'oggetto stesso. Scrivere questo parametro come self è semplicemente una convenzione . Non è una parola chiave e non ha un significato speciale in Python. Potremmo usare altri nomi (come questo) ma ti consiglio vivamente di non farlo. L'uso di nomi diversi da self è disapprovato dalla maggior parte degli sviluppatori e degrada la leggibilità del codice ("La leggibilità conta").
...
In, il primo esempio self.x è un attributo di istanza mentre x è una variabile locale. Non sono gli stessi e si trovano in spazi dei nomi diversi.

Il Sé è qui per restare

Molti hanno proposto di rendere self una parola chiave in Python, come questa in C ++ e Java. Ciò eliminerebbe l'uso ridondante di sé esplicito dall'elenco formale dei parametri nei metodi. Mentre questa idea sembra promettente, non accadrà. Almeno non nel prossimo futuro. Il motivo principale è la compatibilità con le versioni precedenti . Ecco un blog del creatore di Python stesso che spiega perché l'io esplicito deve rimanere.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.