¿Por qué necesita explícitamente tener el argumento "self" en un método Python?

197

Al definir un método en una clase en Python, se ve así:

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

Pero en algunos otros lenguajes, como C #, tiene una referencia al objeto al que está vinculado el método con la palabra clave "this" sin declararlo como un argumento en el prototipo del método.

¿Fue esta una decisión intencional de diseño de lenguaje en Python o hay algunos detalles de implementación que requieren la aprobación de "self" como argumento?

Solo lectura
fuente
15
Apuesto a que también estaría interesado en saber por qué tiene que escribir de forma explícita selfa los miembros de acceso - stackoverflow.com/questions/910020/...
Piotr Dobrogost
1
Pero parece un poco un plato de caldera sin embargo
Raghuveer
Un poco confuso pero vale la pena entender stackoverflow.com/a/31367197/1815624
CrandellWS

Respuestas:

91

Me gusta citar el Zen de Python de Peters. "Explícito es mejor que implícito".

En Java y C ++, ' this.' se puede deducir, excepto cuando tiene nombres de variables que hacen que sea imposible de deducir. Entonces a veces lo necesitas y a veces no.

Python elige hacer que cosas como esta sean explícitas en lugar de basarse en una regla.

Además, dado que no se implica ni se supone nada, se exponen partes de la implementación. self.__class__, self.__dict__y otras estructuras "internas" están disponibles de manera obvia.

S.Lott
fuente
53
Aunque sería bueno tener un mensaje de error menos críptico cuando lo olvides.
Martin Beckett
9
Sin embargo, cuando llama a un método no tiene que pasar la variable de objeto, ¿no rompe la regla de lo explícito? Si para mantener este zen, tiene que ser algo como: object.method (object, param1, param2). Parece de alguna manera inconsistente ...
Vedmant
10
"explícito es mejor que implícito": ¿no es implícito el "estilo" de Python construido en torno a las cosas? por ejemplo, tipos de datos implícitos, límites de funciones implícitas (sin {} 's), alcance de variable implícita ... si las variables globales en los módulos están disponibles en funciones ... ¿por qué no debería aplicarse esa misma lógica / razonamiento a las clases? ¿No sería la regla simplificada simplemente "cualquier cosa declarada en un nivel superior está disponible en un nivel inferior" según lo determinado por la sangría?
Simon
13
"Explicito es mejor que implícito" Detectado sin sentido
Vahid Amiri
10
seamos sinceros, simplemente es malo. No hay excusa para esto. Es solo una reliquia fea pero está bien.
Toskan
63

Es para minimizar la diferencia entre métodos y funciones. Le permite generar fácilmente métodos en metaclases o agregar métodos en tiempo de ejecución a clases preexistentes.

p.ej

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

También (hasta donde yo sé) facilita la implementación del tiempo de ejecución de Python.

Ryan
fuente
10
+1 para Es para minimizar la diferencia entre métodos y funciones. Esta respuesta debe aceptarse
usuario
Esto también en la raíz de la explicación muy vinculada de guido.
Marcin
1
Esto también muestra que en Python, cuando haces c.bar () primero verifica la instancia en busca de atributos, luego verifica los atributos de clase . Por lo tanto, puede 'adjuntar' datos o funciones (objetos) a una Clase en cualquier momento y esperar acceder en su instancia (es decir, dir (instancia) será cómo). No solo cuando "creaste" la instancia c. Es muy dinámico.
Nishant
10
Realmente no lo compro. Incluso en los casos en que necesita la clase padre, aún puede inferirla en la ejecución. Y la equivalencia entre los métodos de instancia y las instancias pasadas de funciones de clase es una tontería; Ruby está bien sin ellos.
zachaysan
2
JavaScript le permite agregar métodos a un objeto en tiempo de ejecución, y no requiere selfen la declaración de función (tenga en cuenta que tal vez esto es arrojar piedras desde una casa de cristal, ya que JavaScript tiene algunas thissemánticas de encuadernación bastante difíciles )
Jonathan Benn
55

Sugiero que se lea el blog de Guido van Rossum sobre este tema: por qué el ser explícito tiene que quedarse .

Cuando se decora una definición de método, no sabemos si asignarle automáticamente un parámetro 'self' o no: el decorador podría convertir la función en un método estático (que no tiene 'self'), o un método de clase (que tiene un tipo divertido de sí mismo que se refiere a una clase en lugar de una instancia), o podría hacer algo completamente diferente (es trivial escribir un decorador que implemente '@classmethod' o '@staticmethod' en Python puro). No hay manera sin saber lo que hace el decorador para dotar al método que se está definiendo con un argumento implícito de "sí mismo" o no.

Rechazo los hacks como las carcasas especiales '@classmethod' y '@staticmethod'.

Bhadra
fuente
16

Python no te obliga a usar "self". Puedes darle el nombre que quieras. Solo debe recordar que el primer argumento en un encabezado de definición de método es una referencia al objeto.

Victor Noagbodji
fuente
Sin embargo, por convención, debería ser 'self' para instancias o 'cls' donde los tipos están involucrados (mmmm metaclases)
pobk
1
Obliga a colocarse como el primer parámetro en cada método, solo texto adicional que no tiene mucho sentido para mí. Otros idiomas funcionan bien con esto.
Vedmant
estoy en lo cierto? siempre el primer parámetro es una referencia al objeto.
Mohammad Mahdi KouchakYazdi
@MMKY No, por ejemplo con @staticmethodél no lo es.
Mark el
1
"Solo tienes que recordar que el primer argumento en la definición de un método ..." Experimenté cambiando la palabra "self" a "kwyjibo" y aún funcionó. Entonces, como se explica a menudo, no es importante la palabra "yo", sino la posición de lo que ocupe ese espacio (?)
RBV
7

También le permite hacer esto: (en resumen, la invocación Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)devolverá 12, pero lo hará de la manera más loca.

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

Por supuesto, esto es más difícil de imaginar en lenguajes como Java y C #. Al hacer explícita la autorreferencia, puede referirse a cualquier objeto mediante esa autorreferencia. Además, tal forma de jugar con las clases en tiempo de ejecución es más difícil de hacer en los idiomas más estáticos, no es necesariamente bueno o malo. Es solo que el ser explícito permite que exista toda esta locura.

Además, imagine esto: nos gustaría personalizar el comportamiento de los métodos (para perfilar, o alguna magia negra loca). Esto puede llevarnos a pensar: ¿qué pasaría si tuviéramos una clase Methodcuyo comportamiento podríamos anular o controlar?

Pues aquí 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()

Y ahora: InnocentClass().magic_method()actuará como se esperaba. El método estará vinculado con el innocent_selfparámetro a InnocentClass, y con la magic_selfinstancia de MagicMethod. Raro eh? Es como tener 2 palabras clave this1y this2en lenguajes como Java y C #. Magia como esta permite que los frameworks hagan cosas que de otro modo serían mucho más detalladas.

Nuevamente, no quiero comentar sobre la ética de estas cosas. Solo quería mostrar cosas que serían más difíciles de hacer sin una autorreferencia explícita.

vlad-ardelean
fuente
44
Cuando considero su primer ejemplo, puedo hacer lo mismo en Java: la clase interna necesita llamar OuterClass.thispara obtener el 'yo' de la clase externa, pero aún puede usarlo thiscomo referencia a sí misma; muy similar a lo que haces aquí en Python. Para mí no fue más difícil imaginar esto. ¿Tal vez depende de la competencia de uno en el idioma en cuestión?
klaar
¿Pero aún puede referirse a los ámbitos cuando se encuentra dentro de un método de una clase anónima, que se define dentro de una clase anónima, que se define dentro de una implementación anónima de la interfaz Something, que a su vez se define dentro de otra implementación anónima Something? En python, por supuesto, puede referirse a cualquiera de los ámbitos.
vlad-ardelean
Tiene razón, en Java solo puede referirse a la clase externa llamando a su nombre de clase explícito y usarlo como prefijo this. Las referencias implícitas son impossibru en Java.
klaar
Sin embargo, me pregunto si esto funcionaría: en cada ámbito (cada método) tiene una variable local, que hace referencia al thisresultado. Por ejemplo Object self1 = this;(use Object, o algo menos genérico). Entonces, si usted tiene acceso a la variable en los alcances más altos, usted podría tener acceso a self1, self2, ... selfn. Creo que estos deberían declararse definitivos o algo así, pero podría funcionar.
vlad-ardelean
3

Creo que la verdadera razón además de "The Zen of Python" es que las funciones son ciudadanos de primera clase en Python.

Lo que esencialmente los convierte en un Objeto. Ahora, el problema fundamental es si sus funciones también son objeto, entonces, en el paradigma orientado a objetos, ¿cómo enviaría mensajes a los objetos cuando los mensajes mismos son objetos?

Parece un problema de huevo de gallina, para reducir esta paradoja, la única forma posible es pasar un contexto de ejecución a los métodos o detectarlo. Pero dado que Python puede tener funciones anidadas, sería imposible hacerlo ya que el contexto de ejecución cambiaría para las funciones internas.

Esto significa que la única solución posible es pasar explícitamente 'self' (El contexto de ejecución).

Así que creo que es un problema de implementación, el Zen llegó mucho más tarde.

pankajdoharey
fuente
Hola, soy nuevo en Python (desde el fondo de Java) y no dije lo que dijiste "cómo enviarías mensajes a los objetos cuando los mensajes en sí mismos son objetos". ¿Por qué es eso un problema? ¿Puedes explicarlo?
Qiulang
1
@Qiulang Aah, en la programación orientada a objetos, los métodos de llamada en objetos son equivalentes a enviar mensajes a objetos con o sin carga útil (parámetros para su función). Los métodos se representarían internamente como un bloque de código asociado con una clase / objeto y utilizarán el entorno implícito disponible a través del objeto contra el que se invoca. Pero si sus métodos son objetos, pueden existir independientemente de estar asociados a una clase / objeto que plantea la pregunta si invoca este método ¿en qué entorno se ejecutaría?
pankajdoharey
Por lo tanto, debe haber un mecanismo para proporcionar un entorno, self significaría el entorno actual en el punto de ejecución, pero también podría proporcionar otro entorno.
pankajdoharey
2

Creo que tiene que ver con PEP 227:

Los nombres en el alcance de la clase no son accesibles. Los nombres se resuelven en el ámbito de la función de encierro más interna. Si se produce una definición de clase en una cadena de ámbitos anidados, el proceso de resolución omite las definiciones de clase. Esta regla evita interacciones extrañas entre los atributos de clase y el acceso a la variable local. Si se produce una operación de enlace de nombre en una definición de clase, crea un atributo en el objeto de clase resultante. Para acceder a esta variable en un método, o en una función anidada dentro de un método, se debe utilizar una referencia de atributo, ya sea por sí mismo o por el nombre de la clase.

daole
fuente
1

Como se explica en sí mismo en Python, desmitificado

cualquier cosa como obj.meth (args) se convierte en Class.meth (obj, args). El proceso de llamada es automático mientras que el proceso de recepción no lo es (es explícito). Esta es la razón por la cual el primer parámetro de una función en la clase debe ser el objeto mismo.

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

Invocaciones

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

init () define tres parámetros pero acabamos de pasar dos (6 y 8). De manera similar, la distancia () requiere uno, pero se pasaron cero argumentos.

¿Por qué Python no se queja de esta discrepancia de número de argumento ?

Generalmente, cuando llamamos a un método con algunos argumentos, la función de clase correspondiente se llama colocando el objeto del método antes del primer argumento. Entonces, cualquier cosa como obj.meth (args) se convierte en Class.meth (obj, args). El proceso de llamada es automático mientras que el proceso de recepción no lo es (es explícito).

Esta es la razón por la cual el primer parámetro de una función en la clase debe ser el objeto mismo. Escribir este parámetro como self es simplemente una convención . No es una palabra clave y no tiene un significado especial en Python. Podríamos usar otros nombres (como este) pero le sugiero encarecidamente que no lo haga. El uso de nombres que no sean self está mal visto por la mayoría de los desarrolladores y degrada la legibilidad del código ("La legibilidad cuenta").
...
En, el primer ejemplo self.x es un atributo de instancia, mientras que x es una variable local. No son lo mismo y se encuentran en diferentes espacios de nombres.

El yo está aquí para quedarse

Muchos han propuesto convertirse en una palabra clave en Python, como esta en C ++ y Java. Esto eliminaría el uso redundante de self explícito de la lista de parámetros formales en los métodos. Si bien esta idea parece prometedora, no va a suceder. Al menos no en el futuro cercano. La razón principal es la compatibilidad con versiones anteriores . Aquí hay un blog del creador del propio Python que explica por qué el yo explícito tiene que quedarse.

Lun
fuente
-5

También hay otra respuesta muy simple: según el zen de python , "explícito es mejor que implícito".

Flávio Amieiro
fuente