yo en Python, desmitificado

Si ha estado programando en Python (programación orientada a objetos) durante algún tiempo, definitivamente se ha encontrado con métodos que tienen selfcomo primer parámetro.

Primero tratemos de entender qué es este parámetro recurrente del yo.

¿Qué es uno mismo en Python?

En la programación orientada a objetos, siempre que definimos métodos para una clase, los usamos selfcomo primer parámetro en cada caso. Veamos la definición de una clase llamada Cat.

 class Cat: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a cat. My name is (self.name). I am (self.age) years old.") def make_sound(self): print("Meow")

En este caso todos los métodos, incluido __init__, tienen el primer parámetro como self.

Sabemos que la clase es un modelo para los objetos. Este plano se puede utilizar para crear varios números de objetos. Creemos dos objetos diferentes de la clase anterior.

 cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)

La selfpalabra clave se usa para representar una instancia (objeto) de la clase dada. En este caso, los dos Catobjetos cat1y cat2tienen sus propios atributos namey age. Si no hubiera un argumento propio, la misma clase no podría contener la información de ambos objetos.

Sin embargo, dado que la clase es solo un plano, selfpermite el acceso a los atributos y métodos de cada objeto en Python. Esto permite que cada objeto tenga sus propios atributos y métodos. Por lo tanto, incluso mucho antes de crear estos objetos, hacemos referencia a los objetos como selfal definir la clase.

¿Por qué se define uno mismo explícitamente cada vez?

Incluso cuando entendemos el uso de self, puede parecer extraño, especialmente para los programadores que vienen de otros lenguajes, que selfse pase como parámetro explícitamente cada vez que definimos un método. Como dice The Zen of Python , " Explícito es mejor que implícito ".

Entonces, ¿por qué necesitamos hacer esto? Para empezar, tomemos un ejemplo sencillo. Tenemos una Pointclase que define un método distancepara calcular la distancia desde el origen.

 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

Instalemos ahora esta clase y encontremos la distancia.

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

En el ejemplo anterior, __init__()define tres parámetros pero acabamos de pasar dos (6 y 8). De manera similar, distance()requiere uno, pero se pasaron cero argumentos. ¿Por qué Python no se queja de esta falta de coincidencia de números de argumento?

¿Qué sucede internamente?

Point.distancey p1.distanceen el ejemplo anterior son diferentes y no exactamente iguales.

 >>> type(Point.distance) >>> type(p1.distance) 

Podemos ver que el primero es una función y el segundo es un método. Una cosa peculiar acerca de los métodos (en Python) es que el objeto mismo se pasa como primer argumento a la función correspondiente.

En el caso del ejemplo anterior, la llamada al método p1.distance()es realmente equivalente a Point.distance(p1).

Generalmente, cuando llamamos a un método con algunos argumentos, se llama a la función de clase correspondiente colocando el objeto del método antes del primer argumento. Entonces, cualquier cosa se obj.meth(args)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 que el primer parámetro de una función en la clase debe ser el objeto en sí. Escribir este parámetro como selfes simplemente una convención. No es una palabra clave y no tiene un significado especial en Python. Podríamos usar otros nombres (como this) pero se desaconseja mucho. La selfmayoría de los desarrolladores desaprueban el uso de nombres distintos de los que degradan la legibilidad del código (la legibilidad cuenta ).

El yo puede evitarse

A estas alturas ya tiene claro que el objeto (instancia) en sí se pasa como primer argumento, automáticamente. Este comportamiento implícito se puede evitar al hacer un método estático . Considere el siguiente ejemplo sencillo:

 class A(object): @staticmethod def stat_meth(): print("Look no self was passed")

Aquí, @staticmethodhay un decorador de funciones que hace stat_meth()static. Creemos una instancia de esta clase y llamemos al método.

 >>> a = A() >>> a.stat_meth() Look no self was passed

En el ejemplo anterior, podemos ver que el comportamiento implícito de pasar el objeto como primer argumento se evitó al usar un método estático. Con todo, los métodos estáticos se comportan como las viejas funciones simples (ya que todos los objetos de una clase comparten métodos estáticos).

 >>> type(A.stat_meth) >>> type(a.stat_meth) 

El yo está aquí para quedarse

Lo explícito selfno es exclusivo de Python. Esta idea fue tomada de Modula-3 . A continuación, se muestra un caso de uso en el que resulta útil.

No hay una declaración de variable explícita en Python. Entran en acción en la primera asignación. El uso de selfhace que sea más fácil distinguir entre atributos de instancia (y métodos) de variables locales.

En el primer ejemplo, self.x es un atributo de instancia mientras que x es una variable local. No son iguales y se encuentran en diferentes espacios de nombres.

Muchos han propuesto hacer de self una palabra clave en Python, como thisen C ++ y Java. Esto eliminaría el uso redundante de explícito selfde la lista de parámetros formales en los métodos.

Si bien esta idea parece prometedora, no va a suceder. Al menos no en un futuro próximo. La razón principal es la compatibilidad con versiones anteriores. Aquí hay un blog del propio creador de Python que explica por qué el yo explícito tiene que quedarse.

__init __ () no es un constructor

Una conclusión importante que se puede extraer de la información hasta ahora es que el __init__()método no es un constructor. Muchos programadores de Python ingenuos se confunden con él, ya que __init__()se llama cuando creamos un objeto.

A closer inspection will reveal that the first parameter in __init__() is the object itself (object already exists). The function __init__() is called immediately after the object is created and is used to initialize it.

Technically speaking, a constructor is a method which creates the object itself. In Python, this method is __new__(). A common signature of this method is:

 __new__(cls, *args, **kwargs)

When __new__() is called, the class itself is passed as the first argument automatically(cls).

Again, like self, cls is just a naming convention. Furthermore, *args and **kwargs are used to take an arbitrary number of arguments during method calls in Python.

Some important things to remember when implementing __new__() are:

  • __new__() is always called before __init__().
  • First argument is the class itself which is passed implicitly.
  • Always return a valid object from __new__(). Not mandatory, but its main use is to create and return an object.

Let's take a look at an example:

 class Point(object): def __new__(cls,*args,**kwargs): print("From new") print(cls) print(args) print(kwargs) # create our object and return it obj = super().__new__(cls) return obj def __init__(self,x = 0,y = 0): print("From init") self.x = x self.y = y

Now, let's now instantiate it.

 >>> p2 = Point(3,4) From new (3, 4) () From init

This example illustrates that __new__() is called before __init__(). We can also see that the parameter cls in __new__() is the class itself (Point). Finally, the object is created by calling the __new__() method on object base class.

In Python, object is the base class from which all other classes are derived. In the above example, we have done this using super().

Use __new__ or __init__?

You might have seen __init__() very often but the use of __new__() is rare. This is because most of the time you don't need to override it. Generally, __init__() is used to initialize a newly created object while __new__() is used to control the way an object is created.

We can also use __new__() to initialize attributes of an object, but logically it should be inside __init__().

One practical use of __new__(), however, could be to restrict the number of objects created from a class.

Suppose we wanted a class SqPoint for creating instances to represent the four vertices of a square. We can inherit from our previous class Point (the second example in this article) and use __new__() to implement this restriction. Here is an example to restrict a class to have only four instances.

 class SqPoint(Point): MAX_Inst = 4 Inst_created = 0 def __new__(cls,*args,**kwargs): if (cls.Inst_created>= cls.MAX_Inst): raise ValueError("Cannot create more objects") cls.Inst_created += 1 return super().__new__(cls)

Una ejecución de muestra:

 >>> p1 = SqPoint(0,0) >>> p2 = SqPoint(1,0) >>> p3 = SqPoint(1,1) >>> p4 = SqPoint(0,1) >>> >>> p5 = SqPoint(2,2) Traceback (most recent call last):… ValueError: Cannot create more objects

Articulos interesantes...