Por que você precisa ter explicitamente o argumento "self" em um método Python?


197

Ao definir um método em uma classe em Python, ele se parece com isso:

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

Mas em alguns outros idiomas, como o C #, você tem uma referência ao objeto ao qual o método está associado à palavra-chave "this" sem declará-lo como argumento no protótipo do método.

Esta foi uma decisão intencional de design de linguagem em Python ou existem alguns detalhes de implementação que exigem a aprovação de "self" como argumento?


15
Aposto que você também estaria interessado em saber por que precisa escrever explicitamente selfpara acessar membros. - stackoverflow.com/questions/910020/…
Piotr Dobrogost

1
Mas parece kinda uma placa de caldeira embora
Raghuveer

Um pouco confuso, mas vale a pena entender stackoverflow.com/a/31367197/1815624
CrandellWS:

Respostas:


91

Eu gosto de citar o Zen de Python de Peters. "Explícito é melhor que implícito."

Em Java e C ++, ' this.' pode ser deduzido, exceto quando você tiver nomes de variáveis ​​que impossibilitem a dedução. Então você às vezes precisa e às vezes não.

O Python decide tornar coisas assim explícitas, e não baseadas em uma regra.

Além disso, como nada é implícito ou assumido, partes da implementação são expostas. self.__class__, self.__dict__E outras estruturas "internos" estão disponíveis de uma forma óbvia.


53
Embora seja bom ter uma mensagem de erro menos enigmática quando você a esquecer.
276 Martin

9
No entanto, quando você chama um método, não precisa passar a variável de objeto, não quebra a regra da explicitação? Para manter esse zen, deve ser algo como: object.method (object, param1, param2). Looks de alguma forma inconsistente ...
Vedmant

10
"explícito é melhor do que implícito" - O "estilo" do Python não é implícito? por exemplo, tipos de dados implícitos, limites implícitos de funções (sem {}), escopo variável implícito ... se variáveis ​​globais em módulos estão disponíveis em funções ... por que essa mesma lógica / raciocínio não deve ser aplicada às classes? A regra simplificada não seria apenas "qualquer coisa declarada em um nível superior está disponível em um nível inferior", conforme determinado pelo recuo?
28416 Simon

13
"Explícito é melhor que implícito" Bobagem detectada
Vahid Amiri 18/17

10
vamos ser sinceros, isso é ruim. Não há desculpa para isso. É apenas uma relíquia feia, mas está tudo bem.
Toskan

62

É para minimizar a diferença entre métodos e funções. Permite gerar métodos facilmente em metaclasses ou adicionar métodos em tempo de execução a classes pré-existentes.

por exemplo

>>> 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!
>>>

Também (até onde eu sei) facilita a implementação do tempo de execução do python.


10
+1 em É minimizar a diferença entre métodos e funções. Resposta deve ser aceita
usuário

Isso também está na raiz da explicação muito ligada do guido.
21413 Marcin

1
Isso também mostra que, no Python, quando você faz c.bar () primeiro, verifica a instância por atributos, depois verifica os atributos da classe . Assim, você pode 'anexar' um dado ou função (objetos) a uma Classe a qualquer momento e esperar acessar em sua instância (por exemplo, dir (instância) será como). Não apenas quando você "criou" c instância. É muito dinâmico.
Nishant

10
Eu realmente não compro. Mesmo nos casos em que você precisa da classe pai, ainda pode deduzi-la na execução. E a equivalência entre métodos de instância e funções de classe transmitidas por instâncias é tola; Ruby se sai bem sem eles.
zachaysan

2
O JavaScript permite que você adicione métodos a um objeto em tempo de execução, e não exige selfna declaração da função (lembre-se, talvez isso esteja atirando pedras de uma casa de vidro, pois o JavaScript tem algumas thissemânticas de ligação bastante complicadas )
Jonathan Benn

55

Eu sugiro que se deva ler o blog de Guido van Rossum sobre este tópico - Por que o eu explícito tem que ficar .

Quando uma definição de método é decorada, não sabemos se deve ou não fornecer automaticamente um parâmetro 'self': o decorador pode transformar a função em um método estático (que não tem 'self') ou em um método de classe (que tem um tipo engraçado de identidade que se refere a uma classe em vez de uma instância) ou pode fazer algo completamente diferente (é trivial escrever um decorador que implemente '@classmethod' ou '@staticmethod' em Python puro). Não há como saber o que o decorador faz se dota o método que está sendo definido com um argumento implícito de 'eu' ou não.

Rejeito hacks como o caso especial '@classmethod' e '@staticmethod'.


16

O Python não força você a usar o "self". Você pode dar o nome que quiser. Você apenas precisa lembrar que o primeiro argumento em um cabeçalho de definição de método é uma referência ao objeto.


Por convenção, no entanto, deve ser 'self' para instâncias ou 'cls' em que tipos estão envolvidos (metaclasses mmmm)
pobk

1
Obriga a colocar o self como primeiro parâmetro em todos os métodos, apenas um texto extra que não faz muito sentido para mim. Outros idiomas funcionam bem com isso.
Vedmant

Estou certo ? sempre o primeiro parâmetro é uma referência ao objeto.
Mohammad Mahdi KouchakYazdi -

@MMKY Não, por exemplo, com @staticmethodele não é.
Mark

1
"Você apenas tem que lembrar que o primeiro argumento em uma definição de método ..." Eu experimentei mudar a palavra "eu" para "kwyjibo" e ainda funcionava. Então, como é muitas vezes explicado, não é a palavra "eu" que é importante, mas a posição de qualquer ocupa esse espaço (?)
RBV

7

Também permite que você faça o seguinte: (em resumo, chamar Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)retornará 12, mas o fará da maneira mais louca possível.

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

Obviamente, isso é mais difícil de imaginar em linguagens como Java e C #. Ao tornar explícita a auto-referência, você pode se referir a qualquer objeto por essa auto-referência. Além disso, essa maneira de brincar com as classes em tempo de execução é mais difícil nas linguagens mais estáticas - não é necessariamente boa ou ruim. Só que o eu explícito permite que toda essa loucura exista.

Além disso, imagine o seguinte: gostaríamos de personalizar o comportamento dos métodos (para criação de perfil ou alguma magia negra louca). Isso pode nos levar a pensar: e se tivéssemos uma classe Methodcujo comportamento poderíamos substituir ou controlar?

Bem, aqui está:

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 agora: InnocentClass().magic_method()agirá como esperado. O método será vinculado ao innocent_selfparâmetro to InnocentClasse magic_selfà instância MagicMethod. Estranho né? É como ter duas palavras this1- chave e this2em linguagens como Java e C #. Magias como essa permitem que estruturas façam coisas que seriam muito mais detalhadas.

Novamente, não quero comentar sobre a ética dessas coisas. Eu só queria mostrar coisas que seriam mais difíceis de fazer sem uma auto-referência explícita.


4
Quando considero seu primeiro exemplo, posso fazer o mesmo em Java: a classe interna precisa chamar OuterClass.thispara obter o 'eu' da classe externa, mas você ainda pode usar thiscomo referência a si mesma; muito parecido com o que você faz aqui em Python. Para mim, não foi mais difícil imaginar isso. Talvez isso dependa da proficiência de alguém no idioma em questão?
precisa saber é o seguinte

Mas você ainda pode se referir a qualquer escopo quando estiver dentro de um método de uma classe anônima, definida dentro de uma classe anônima, definida dentro de uma implementação anônima da interface Something, que por sua vez é definida dentro de outra implementação anônima Something? Em python, é claro, você pode se referir a qualquer um dos escopos.
precisa saber é o seguinte

Você está certo, em Java, você pode apenas se referir à classe externa chamando seu nome de classe explícito e usá-lo como prefixo this. Referências implícitas são impossíveis em Java.
Klaar

Gostaria de saber se isso funcionaria: Em cada escopo (cada método) tem uma variável local, que faz referência ao thisresultado. Por exemplo Object self1 = this;(use Object ou algo menos genérico). Então, se você tem acesso à variável nos âmbitos superiores, você poderia ter acesso a self1, self2... selfn. Eu acho que estes devem ser declarados finais ou algo assim, mas pode funcionar.
Valor-Ardelean

3

Eu acho que a verdadeira razão além de "O Zen do Python" é que as funções são cidadãos de primeira classe em Python.

O que essencialmente os torna um objeto. Agora, a questão fundamental é se suas funções também são objetos, no paradigma orientado a objetos, como você enviaria mensagens para objetos quando as próprias mensagens fossem objetos?

Parece um problema de ovo de galinha. Para reduzir esse paradoxo, a única maneira possível é passar um contexto de execução para métodos ou detectá-lo. Mas como o python pode ter funções aninhadas, seria impossível fazê-lo, pois o contexto de execução mudaria para funções internas.

Isso significa que a única solução possível é passar explicitamente 'self' (o contexto de execução).

Então eu acredito que é um problema de implementação que o Zen veio muito mais tarde.


Olá, sou novo no python (a partir do plano de fundo java) e não flui exatamente o que você disse "como enviar mensagens para objetos quando as próprias mensagens são objetos". Por que isso é um problema, você pode elaborar?
Qiulang

1
@Qiulang Aah, na programação orientada a objetos, chamar métodos em objetos é equivalente a enviar mensagens para objetos com ou sem carga (parâmetros para sua função). Métodos internamente seriam representados como um bloco de código associado a uma classe / objeto e usa o ambiente implícito disponível através do objeto no qual é invocado. Mas se seus métodos são objetos, eles podem existir independentemente de estarem associados a uma classe / objeto que levanta a questão se você invocar esse método em qual ambiente ele seria executado?
pankajdoharey

Portanto, deve haver um mecanismo para fornecer um ambiente, o próprio significaria o ambiente atual no ponto de execução, mas você também poderia fornecer outro ambiente.
precisa saber é o seguinte

2

Eu acho que tem a ver com PEP 227:

Os nomes no escopo da classe não estão acessíveis. Os nomes são resolvidos no escopo da função mais interna. Se uma definição de classe ocorrer em uma cadeia de escopos aninhados, o processo de resolução ignorará as definições de classe. Essa regra evita interações ímpares entre atributos de classe e acesso a variáveis ​​locais. Se uma operação de ligação de nome ocorrer em uma definição de classe, ela criará um atributo no objeto de classe resultante. Para acessar essa variável em um método ou em uma função aninhada em um método, uma referência de atributo deve ser usada, seja por conta própria ou pelo nome da classe.


1

Conforme explicado em si mesmo em Python, Desmistificado

qualquer coisa como obj.meth (args) se torna Class.meth (obj, args). O processo de chamada é automático enquanto o processo de recebimento não é (explícito). Essa é a razão pela qual o primeiro parâmetro de uma função na classe deve ser o próprio objeto.

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

Invocações:

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

init () define três parâmetros, mas passamos dois (6 e 8). Da mesma forma, distance () requer um, mas nenhum argumento foi passado.

Por que o Python não está reclamando dessa incompatibilidade de número de argumento ?

Geralmente, quando chamamos um método com alguns argumentos, a função de classe correspondente é chamada colocando o objeto do método antes do primeiro argumento. Portanto, qualquer coisa como obj.meth (args) se torna Class.meth (obj, args). O processo de chamada é automático enquanto o processo de recebimento não é (explícito).

Esse é o motivo pelo qual o primeiro parâmetro de uma função na classe deve ser o próprio objeto. Escrever esse parâmetro como próprio é apenas uma convenção . Não é uma palavra-chave e não possui significado especial no Python. Poderíamos usar outros nomes (como este), mas sugiro fortemente que não. O uso de nomes diferentes de self é desaprovado pela maioria dos desenvolvedores e prejudica a legibilidade do código ("Legibilidade conta").
...
Em, o primeiro exemplo self.x é um atributo de instância enquanto x é uma variável local. Eles não são os mesmos e estão em diferentes namespaces.

O eu está aqui para ficar

Muitos propuseram tornar-se uma palavra-chave em Python, como esta em C ++ e Java. Isso eliminaria o uso redundante do self explícito da lista formal de parâmetros nos métodos. Embora essa idéia pareça promissora, não vai acontecer. Pelo menos não no futuro próximo. O principal motivo é a compatibilidade com versões anteriores . Aqui está um blog do criador do próprio Python, explicando por que o eu explícito precisa permanecer.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.