Python 3 - Orientado a objetos

Python ha sido un lenguaje orientado a objetos desde el momento en que existió. Debido a esto, crear y usar clases y objetos es francamente fácil. Este capítulo le ayuda a convertirse en un experto en el uso del soporte de programación orientada a objetos de Python.

Si no tiene experiencia previa con la programación orientada a objetos (OO), es posible que desee consultar un curso introductorio o al menos un tutorial de algún tipo para que comprenda los conceptos básicos.

Sin embargo, aquí hay una pequeña introducción a la programación orientada a objetos (OOP) para ayudarlo:

Descripción general de la terminología de OOP

  • Class- Un prototipo definido por el usuario para un objeto que define un conjunto de atributos que caracterizan a cualquier objeto de la clase. Los atributos son miembros de datos (variables de clase y variables de instancia) y métodos, a los que se accede mediante notación de puntos.

  • Class variable: Una variable que comparten todas las instancias de una clase. Las variables de clase se definen dentro de una clase pero fuera de cualquiera de los métodos de la clase. Las variables de clase no se utilizan con tanta frecuencia como las variables de instancia.

  • Data member - Una variable de clase o variable de instancia que contiene datos asociados con una clase y sus objetos.

  • Function overloading- La asignación de más de un comportamiento a una función determinada. La operación realizada varía según los tipos de objetos o argumentos involucrados.

  • Instance variable - Una variable que se define dentro de un método y pertenece solo a la instancia actual de una clase.

  • Inheritance - La transferencia de las características de una clase a otras clases que se deriven de ella.

  • Instance- Un objeto individual de una determinada clase. Un objeto obj que pertenece a una clase Circle, por ejemplo, es una instancia de la clase Circle.

  • Instantiation - La creación de una instancia de una clase.

  • Method - Un tipo especial de función que se define en una definición de clase.

  • Object- Una instancia única de una estructura de datos definida por su clase. Un objeto comprende tanto miembros de datos (variables de clase y variables de instancia) como métodos.

  • Operator overloading - La asignación de más de una función a un operador en particular.

Creando clases

La declaración de clase crea una nueva definición de clase. El nombre de la clase sigue inmediatamente a la clase de palabra clave seguida de dos puntos de la siguiente manera:

class ClassName:
   'Optional class documentation string'
   class_suite
  • La clase tiene una cadena de documentación, a la que se puede acceder a través de ClassName.__doc__.

  • los class_suite consta de todas las declaraciones de componentes que definen miembros de clase, atributos de datos y funciones.

Ejemplo

A continuación se muestra un ejemplo de una clase Python simple:

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)
  • La variable empCount es una variable de clase cuyo valor se comparte entre todas las instancias de a en esta clase. Se puede acceder a esto como Employee.empCount desde dentro o fuera de la clase.

  • El primer método __init __ () es un método especial, que se llama constructor de clase o método de inicialización que Python llama cuando crea una nueva instancia de esta clase.

  • Declaras otros métodos de clase como funciones normales con la excepción de que el primer argumento de cada método es self . Python agrega el argumento self a la lista por usted; no es necesario incluirlo cuando llame a los métodos.

Crear objetos de instancia

Para crear instancias de una clase, llama a la clase usando el nombre de la clase y pasa los argumentos que acepta su método __init__ .

This would create first object of Employee class
emp1 = Employee("Zara", 2000)
This would create second object of Employee class
emp2 = Employee("Manni", 5000)

Acceder a los atributos

Puede acceder a los atributos del objeto utilizando el operador de puntos con objeto. Se accedería a la variable de clase usando el nombre de la clase de la siguiente manera:

emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Ahora, juntando todos los conceptos -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)


#This would create first object of Employee class"
emp1 = Employee("Zara", 2000)
#This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Name :  Zara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

Puede agregar, eliminar o modificar atributos de clases y objetos en cualquier momento:

emp1.salary = 7000  # Add an 'salary' attribute.
emp1.name = 'xyz'  # Modify 'age' attribute.
del emp1.salary  # Delete 'age' attribute.

En lugar de usar las declaraciones normales para acceder a los atributos, puede usar las siguientes funciones:

  • los getattr(obj, name[, default]) - para acceder al atributo de objeto.

  • los hasattr(obj,name) - para comprobar si un atributo existe o no.

  • los setattr(obj,name,value)- para establecer un atributo. Si el atributo no existe, se creará.

  • los delattr(obj, name) - para eliminar un atributo.

hasattr(emp1, 'salary')    # Returns true if 'salary' attribute exists
getattr(emp1, 'salary')    # Returns value of 'salary' attribute
setattr(emp1, 'salary', 7000) # Set attribute 'salary' at 7000
delattr(emp1, 'salary')    # Delete attribute 'salary'

Atributos de clase integrados

Cada clase de Python sigue los atributos incorporados y se puede acceder a ellos usando el operador de puntos como cualquier otro atributo:

  • __dict__ - Diccionario que contiene el espacio de nombres de la clase.

  • __doc__ - Cadena de documentación de clase o ninguna, si no está definida.

  • __name__ - Nombre de la clase.

  • __module__- Nombre del módulo en el que se define la clase. Este atributo es "__main__" en modo interactivo.

  • __bases__ - Una tupla posiblemente vacía que contiene las clases base, en el orden en que aparecen en la lista de clases base.

Para la clase anterior, intentemos acceder a todos estos atributos:

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)

emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {
   'displayCount': <function Employee.displayCount at 0x0160D2B8>, 
   '__module__': '__main__', '__doc__': 'Common base class for all employees', 
   'empCount': 2, '__init__': 
   <function Employee.__init__ at 0x0124F810>, 'displayEmployee': 
   <function Employee.displayEmployee at 0x0160D300>,
   '__weakref__': 
   <attribute '__weakref__' of 'Employee' objects>, '__dict__': 
   <attribute '__dict__' of 'Employee' objects>
}

Destrucción de objetos (recolección de basura)

Python elimina los objetos innecesarios (tipos integrados o instancias de clases) automáticamente para liberar espacio en la memoria. El proceso mediante el cual Python recupera periódicamente bloques de memoria que ya no están en uso se denomina recolección de basura.

El recolector de basura de Python se ejecuta durante la ejecución del programa y se activa cuando el recuento de referencias de un objeto llega a cero. El recuento de referencias de un objeto cambia a medida que cambia el número de alias que apuntan a él.

El recuento de referencias de un objeto aumenta cuando se le asigna un nuevo nombre o se coloca en un contenedor (lista, tupla o diccionario). El recuento de referencias del objeto disminuye cuando se elimina con del , su referencia se reasigna o su referencia sale del alcance. Cuando el recuento de referencias de un objeto llega a cero, Python lo recopila automáticamente.

a = 40      # Create object <40>
b = a       # Increase ref. count  of <40> 
c = [b]     # Increase ref. count  of <40> 

del a       # Decrease ref. count  of <40>
b = 100     # Decrease ref. count  of <40> 
c[0] = -1   # Decrease ref. count  of <40>

Normalmente no se dará cuenta cuando el recolector de basura destruya una instancia huérfana y reclame su espacio. Sin embargo, una clase puede implementar el método especial __del __ () , llamado destructor, que se invoca cuando la instancia está a punto de ser destruida. Este método puede usarse para limpiar cualquier recurso que no sea de memoria usado por una instancia.

Ejemplo

Este destructor __del __ () imprime el nombre de clase de una instancia que está a punto de ser destruida -

#!/usr/bin/python3

class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print (class_name, "destroyed")

pt1 = Point()
pt2 = pt1
pt3 = pt1
print (id(pt1), id(pt2), id(pt3))   # prints the ids of the obejcts
del pt1
del pt2
del pt3

Cuando se ejecuta el código anterior, produce el siguiente resultado:

140338326963984 140338326963984 140338326963984
Point destroyed

Note- Idealmente, debería definir sus clases en un archivo separado, luego debería importarlas en su archivo de programa principal usando la declaración de importación .

En el ejemplo anterior, asumiendo que la definición de una clase Point está contenida en point.py y no hay otro código ejecutable en él.

#!/usr/bin/python3
import point

p1 = point.Point()

Herencia de clase

En lugar de comenzar desde cero, puede crear una clase derivándola de una clase preexistente enumerando la clase principal entre paréntesis después del nombre de la nueva clase.

La clase secundaria hereda los atributos de su clase principal y puede usar esos atributos como si estuvieran definidos en la clase secundaria. Una clase secundaria también puede anular los métodos y miembros de datos del padre.

Sintaxis

Las clases derivadas se declaran de forma muy similar a su clase padre; sin embargo, se proporciona una lista de clases base para heredar después del nombre de la clase:

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

Ejemplo

#!/usr/bin/python3

class Parent:        # define parent class
   parentAttr = 100
   def __init__(self):
      print ("Calling parent constructor")

   def parentMethod(self):
      print ('Calling parent method')

   def setAttr(self, attr):
      Parent.parentAttr = attr

   def getAttr(self):
      print ("Parent attribute :", Parent.parentAttr)

class Child(Parent): # define child class
   def __init__(self):
      print ("Calling child constructor")

   def childMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.childMethod()      # child calls its method
c.parentMethod()     # calls parent's method
c.setAttr(200)       # again call parent's method
c.getAttr()          # again call parent's method

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

De manera similar, puede conducir una clase de varias clases principales de la siguiente manera:

class A:        # define your class A
.....

class B:         # define your calss B
.....

class C(A, B):   # subclass of A and B
.....

Puede usar las funciones issubclass () o isinstance () para verificar las relaciones de dos clases e instancias.

  • los issubclass(sub, sup) La función booleana devuelve Verdadero, si la subclase dada sub es de hecho una subclase de la superclase sup.

  • los isinstance(obj, Class)La función booleana devuelve True, si obj es una instancia de la clase Class o es una instancia de una subclase de Class

Métodos primordiales

Siempre puedes anular los métodos de tu clase principal. Una razón para anular los métodos de los padres es que es posible que desee una funcionalidad especial o diferente en su subclase.

Ejemplo

#!/usr/bin/python3

class Parent:        # define parent class
   def myMethod(self):
      print ('Calling parent method')

class Child(Parent): # define child class
   def myMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.myMethod()         # child calls overridden method

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Calling child method

Métodos de sobrecarga de bases

La siguiente tabla enumera algunas funciones genéricas que puede anular en sus propias clases:

No Señor. Método, descripción y llamada de muestra
1

__init__ ( self [,args...] )

Constructor (con argumentos opcionales)

Llamada de muestra: obj = className (args)

2

__del__( self )

Destructor, elimina un objeto

Llamada de muestra: del obj

3

__repr__( self )

Representación de cadena evaluable

Llamada de muestra: repr (obj)

4

__str__( self )

Representación de cadena imprimible

Llamada de muestra: str (obj)

5

__cmp__ ( self, x )

Comparación de objetos

Llamada de muestra: cmp (obj, x)

Operadores de sobrecarga

Suponga que ha creado una clase Vector para representar vectores bidimensionales. ¿Qué sucede cuando usa el operador más para agregarlos? Lo más probable es que Python te grite.

Sin embargo, podría definir el método __add__ en su clase para realizar la suma de vectores y luego el operador más se comportaría según las expectativas:

Ejemplo

#!/usr/bin/python3

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

Cuando se ejecuta el código anterior, produce el siguiente resultado:

Vector(7,8)

Ocultar datos

Los atributos de un objeto pueden ser visibles o no fuera de la definición de clase. Debe nombrar los atributos con un prefijo de subrayado doble, y esos atributos no serán directamente visibles para los forasteros.

Ejemplo

#!/usr/bin/python3

class JustCounter:
   __secretCount = 0
  
   def count(self):
      self.__secretCount += 1
      print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)

Cuando se ejecuta el código anterior, produce el siguiente resultado:

1
2
Traceback (most recent call last):
   File "test.py", line 12, in <module>
      print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'

Python protege a esos miembros cambiando internamente el nombre para incluir el nombre de la clase. Puede acceder a atributos como object._className__attrName . Si reemplaza su última línea de la siguiente manera, entonces funciona para usted:

.........................
print (counter._JustCounter__secretCount)

Cuando se ejecuta el código anterior, produce el siguiente resultado:

1
2
2