7

sea el siguiente código de una sencilla app en kivy:

from kivy.config import Config
Config.set('kivy', 'keyboard_mode', 'system')

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import StringProperty


Builder.load_file('design2.kv')

class MyWidget(BoxLayout):
    def __init__(self):
        super(MyWidget, self).__init__()
        self.showtext() #Llamamos al método desde el constructor

    def showtext(self):
        with open("Prueba.txt","r") as f:
            self.ids['Label1'].text = f.read()


class myApp(App):
    def build(self):
        return MyWidget()
    def on_pause(self):
        return True
    def on_resume(self):
        pass


if __name__ in ('__main__', '__android__'):
    myApp().run()

Sea el archivo .kv (aunque no viene al caso):

<MyWidget>:
    BoxLayout:
        Label:
            id: Label1

Me pregunto por el siguiente fragmento de código del primer código:

class MyWidget(BoxLayout):
    def __init__(self):
        super(MyWidget, self).__init__()
        self.showtext() #Llamamos al método desde el constructor

Según tengo entendido, def __init__(self): es un constructor. ¿Para que sirve un constructor? La otra pregunta es sobre la línea: super(MyWidget, self).__init__() ¿Qué es super?

Rubén
  • 10,857
  • 6
  • 35
  • 79
Mr. Baldan
  • 1,375
  • 5
  • 22
  • 45
  • 1
    Googleando un poco puedes encontrar la respuesta facilmente. Básicamente, sin saber de kivy, estás creando una clase MyWidget que hereda de otra clase BoxLayout. Al hacer `super(...)` estás llamando al constructor de la clase BoxLayout con los parámaetros que recibe el contructor de la clase MyWidget. Finalmente el constructor (es parecido a un método de clase, aunque hay debate sobre esto) que se ejecuta al instanciar un objeto de la clase. – Jose Hermosilla Rodrigo Apr 17 '17 at 19:49
  • Se debe evitar forzar las etiquetas en el título. Referencia https://es.stackoverflow.com/help/tagging – Rubén Apr 19 '17 at 02:11

1 Answers1

18

La pregunta en realidad no es específica de Kivy sino de Python y del paradigma de la POO (Programación orientada a objetos) en general.

La respuesta corta sería que aunque el método __init__ es a veces llamado "constructor" en realidad es solo un inicializador de la instancia, se usa generalmente para inicializar los atributos del objeto que creamos y se ejecuta de forma automática nada más instanciar la clase. En tu caso, como dentro de él llamamos al método showtext el texto ya está cargado en el label cuando nuestra app se inicia.

super se usa en este caso para llamar al inicializador de la clase padre BoxLayout. En este caso es equivalente a BoxLayout.__init__(self, *args, **kwargs).


La respuesta larga va a continuación, toda ella está orientada a Python 3, aunque todo es aplicable a las clases de nuevo estilo de Python 2 (explícitamente derivan de object):

Si nos ponemos estrictos, el término 'constructor' en Python puede ser algo ambiguo. Cuando creamos un objeto se implican dos métodos especiales, lo primero que se llama es el método __new__ y posteriormente se llama al método __init__:

  • __new__: básicamente lo que hace es retornar una instancia válida de la clase. Solo construye un objeto de su clase y la retorna, por lo que éste debería ser considerado el verdadero "constructor" de la clase. Recibe una referencia a la clase como primer argumento (cls).

    Sobrescribir este método no es tan común como en el caso de __init__, generalmente, su uso es personalizar la instanciación de de la clase, por ejemplo es una de las formas de crear un singleton:

    class Singleton:
       _instance = None
    
       def __new__(cls, *args, **kwargs):
           if not cls._instance:
               cls._instance = super().__new__(cls, *args, **kwargs)
           return cls._instance
    

    >>> a = Singleton()
    >>> b = Singleton()
    >>> a
    <__main__.Singleton object at 0x7f79ba4022e8>
    >>> b
    <__main__.Singleton object at 0x7f79ba4022e8>
    

    Si sobrescribimos el método debemos obtener la nueva instancia llamando al __new__ del padre (que proviene de la clase object de la que deriva toda clase de nuevo estilo), por ejemplo:

    instance = super().__new__(cls, *args, **kwargs)
    
  • __init__: si __new__ retorna una instancia de su clase (lo normal) seguidamente se ejecuta implícitamente el método __init__ al que le pasa la instancia recien creada como primer argumento (self por convención). Si __new__ no retorna una instancia de la clase no es llamado y hay que hacerlo de forma explícita. Algunas de sus características son:

    • No puede ser llamado nuevamente.
    • No puede retorna nada.
    • Puede recibir parámetros que se utilizan normalmente para inicializar atributos de instancia.
    • Es opcional.

En muchos lenguajes ambos métodos están unidos en uno y es considerado como constructor. Te puedes encontrar peleas dialécticas al respecto por ahí, hay una tendencia importante a llamar constructor al __init__ aunque no sea estrictamente correcto.

El objetivo fundamental de __init__ es inicializar los atributos del objeto que creamos:

class Círculo:
    pi = 3.1415
    def __init__(self, radio: float):
        self.radio = radio

    def obtener_área(self) -> float:
        print(self.pi * self.radio ** 2)

>>> c = Círculo(4)
>>> c.obtener_área()
50.264

El __init__ en este caso crea e inicializa el atributo de instancia radio, propiedad de todo objeto Círculo y que lo diferencia de otros círculos.

Podemos crear un atributo fuera del __init__ o de cualquier método como puedes haber observado, este es el caso de pi en el ejemplo anterior. Hay una diferencia sustancial en ambos casos, el atributo pi es un atributo que pertenece a la clase y compartido por todas las instancias de la clase que hagamos. radio en cambio es creado por __init__ para la instancia, pertenece al objeto en exclusiva inicializandola dándole unas propiedades diferenciadoras. Para más información la siguiente pregunta puede ser de ayuda:

Diferencia entre atributos de instancia y atributos de clase

Ya para terminar esto, hablar de super ya que es usado comúnmente en el inicializador de la clase, cómo en el ejemplo que muestras. Esto se debe a que al definir nuestro propio inicializador sobrescribimos el __init__ de la clase padre. Para que nuestra clase herede todas las características de su padre implementadas en su inicializador (el cual hemos sobrescrito) es necesario por tanto llamarlo de forma explícita. De esto se encarga precisamente super(MyWidget, self).__init__().

En general permite hacer referencia explícita a la clase padre, por lo que permite delegar la llamada de un método a la clase (o clases) padre.

class A():
    def __init__(self):
        print("Soy el __init__ de la clase A")

class B(A):
    def __init__(self):
        super(B, self).__init__()
        print ("Soy el __init__ de la clase B")

obj = B()

Salida:

Soy el __init__ de la clase A
Soy el __init__ de la clase B

En casos de herencia múltiple es dónde super tiene su potencial verdadero ya que va a buscar el método entre las clases padre en un orden determinada (Method Resolution Order), pero esto ya está muy alejado de la pregunta.

Nota: en Python 3 no es necesario pasar las referencias a la clase y a la instancia a super: super().__init__()

FJSevilla
  • 55,603
  • 7
  • 35
  • 58