Clase base abstracta (abc) en Python

¿Ha pensado alguna vez en comprobar si los objetos que está utilizando se adhieren a una especificación particular? Es necesario verificar si un objeto implementa un método o propiedad determinada, especialmente al crear una biblioteca donde otros desarrolladores hacen uso de ella. Un desarrollador puede usar los métodos hasattr o isinstance  para verificar si la entrada se ajusta a una identidad particular. Pero a veces es un inconveniente usar esos métodos para verificar una gran cantidad de propiedades y métodos diferentes.    

Como solución a este inconveniente, Python introdujo un concepto llamado clase base abstracta (abc) . En esta sección, discutiremos la clase base abstracta y su importancia.  

  • Clase base abstracta
  • Declarar una clase base abstracta
  • ¿Por qué declarar una clase base abstracta?
  • Propiedades abstractas
  • Clases abstractas incorporadas

Clase base abstracta

El objetivo principal de la clase base abstracta es proporcionar una forma estandarizada de probar si un objeto se adhiere a una especificación dada. También puede evitar cualquier intento de instanciar una subclase que no invalide un método particular en la superclase. Y finalmente, usando una clase abstracta, una clase puede derivar la identidad de otra clase sin ninguna herencia de objetos.

Declarar una clase base abstracta

Python tiene un módulo llamado abc (clase base abstracta) que ofrece las herramientas necesarias para crear una clase base abstracta. En primer lugar, debe comprender la metaclase ABCMeta proporcionada por la clase base abstracta. La regla es que cada clase abstracta debe usar la metaclase ABCMeta.

La metaclase ABCMeta proporciona un método llamado método de registro que puede ser invocado por su instancia. Al usar este método de registro, cualquier clase base abstracta puede convertirse en un ancestro de cualquier clase concreta arbitraria. Entendamos este proceso considerando un ejemplo de una clase base abstracta que se registra como un ancestro de dict.

Python3

import abc
  
  
class AbstractClass(metaclass=abc.ABCMeta):
    def abstractfunc(self):
        return None
  
  
print(AbstractClass.register(dict))

Producción:

<class 'dict'>

Aquí, dict se identifica como una subclase de AbstractClass. Hagamos una comprobación.

Python3

import abc
  
  
class AbstractClass(metaclass=abc.ABCMeta):
    def abstractfunc(self):
        return None
  
  
AbstractClass.register(dict)
print(issubclass(dict, AbstractClass))

Producción:

True

¿Por qué declarar una clase base abstracta?

Para comprender la necesidad de declarar una subclase virtual, debemos considerar el ejemplo de un objeto similar a una lista en el que no desea poner una restricción de solo considerar la lista o la tupla. Antes de eso, veamos cómo usar isinstance para verificar una lista o tupla de clase. 

isinstance([], (list, tuple))

Esta verificación de instancia cumple el propósito si acepta solo una lista o una tupla. Pero aquí el caso es diferente, no existe tal restricción. Entonces, esta solución no es extensible para un desarrollador que usa su biblioteca para enviar algo más que una lista o una tupla. Aquí viene la importancia de la clase abstracta. Vamos a entender a través del siguiente código.

Python3

import abc
  
  
class MySequence(metaclass=abc.ABCMeta):
    pass
  
MySequence.register(list)
MySequence.register(tuple)
  
a = [1, 2, 3]
b = ('x', 'y', 'z')
  
print('List instance:', isinstance(a, MySequence))
print('Tuple instance:', isinstance(b, MySequence))
print('Object instance:', isinstance(object(), MySequence))

Producción:

List instance: True
Tuple instance: True
Object instance: False

Como puede ver, cuando realiza una comprobación de instancia, devuelve verdadero tanto para la lista como para la tupla; para los otros objetos, devuelve falso. Consideremos un escenario en el que un desarrollador espera un objeto de clase en sí mismo. En el caso anterior, la instancia devolverá falso. Pero se puede lograr creando una clase personalizada y registrándola con la clase base abstracta. 

Aquí ‘MySequence’ es una clase abstracta dentro de la biblioteca. Un desarrollador puede importarlo y registrar una clase personalizada. Echemos un vistazo al siguiente código.

Python3

import abc
  
  
class MySequence(metaclass=abc.ABCMeta):
    pass
  
class CustomListLikeObjCls(object):
    pass
  
MySequence.register(CustomListLikeObjCls)
print(issubclass(CustomListLikeObjCls, MySequence))

Producción:

True

Aquí, la instancia de CustomListLikeObjCls se pasa a la biblioteca registrándola con MySequence. Por lo tanto, la verificación de la instancia devuelve True. Además del método anterior, también puede usar el método de registro como decorador para registrar una clase personalizada. Veamos cómo usar el método de registro como decorador .

Python3

import abc
  
  
class MySequence(metaclass=abc.ABCMeta):
    pass
  
@MySequence.register
class CustomListLikeObjCls(object):
    pass
  
print(issubclass(CustomListLikeObjCls, MySequence))

Producción:

True

Registrar una clase utilizando el método implementado anteriormente cumple el propósito. Sin embargo, debe realizar un registro manual para cada subclase prevista. ¿Qué tal la subclasificación automática basada en un método particular? Una clase abstracta tiene un concepto llamado __subclasshook__   para subclasificar las clases.  

__subclasshook___

Es un método mágico especial definido por ABCMeta. El __subclasshook__ debe definirse como un método de clase usando el decorador @classmethod. Toma un argumento posicional adicional además de la clase y puede devolver cualquiera de los tres valores: verdadero, falso o no implementado. Veamos la siguiente implementación.

Python3

import abc
  
  
class AbstractClass(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(cls, other):
        print('subclass hook:', other)
        hookmethod = getattr(other, 'hookmethod', None)
        return callable(hookmethod)
  
class SubClass(object):
    def hookmethod(self):
        pass
  
class NormalClass(object):
    hookmethod = 'hook'
  
  
print(issubclass(SubClass, AbstractClass))
print(issubclass(NormalClass, AbstractClass))

Producción:

subclass hook: <class '__main__.SubClass'>
True
subclass hook: <class '__main__.NormalClass'>
False

De la discusión anterior, comprendió cómo enlazar subclases automáticamente. Ahora veremos cómo evitar instanciar una subclase que no anula un método particular en la superclase. Esta característica se puede lograr usando @abc.abstractmethod.

@abc.abstractmethod

@abc.abstractmethod evita cualquier intento de instanciar una subclase que no anula un método particular en la superclase. Echemos un vistazo al siguiente código:

Python3

import abc
  
  
class AbstractClass(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def abstractName(self):
        pass
  
class InvalidSubClass(AbstractClass):
    pass
  
isc = InvalidSubClass()

Dado que InvalidSubclass no anula el método abstractName, @abc.abstractmethod evita que se cree una instancia de la subclase y arroja el siguiente error.

Rastreo (última llamada más reciente):
 Archivo «/home/553d5199a662239eae3ff58efb37b6ec.py», línea 11, en <módulo>
   isc = InvalidSubClass()
TypeError: No se puede crear instancias de la clase abstracta InvalidSubClass con métodos abstractos abstractName

Veamos otro ejemplo en el que la subclase anula el método abstracto.

Python3

import abc
  
class AbstractClass(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def abstractName(self):
        pass
  
class ValidSubClass(AbstractClass):
    def abstractName(self):
        return 'Abstract 1'
  
vc = ValidSubClass()
print(vc.abstractName())

Producción:

Abstract 1

A continuación, veamos cómo declarar propiedades como una clase abstracta.

Propiedades abstractas

Podemos usar @property decorator y @abc.abstractmethod para declarar propiedades como una clase abstracta. Veamos el siguiente código.

Python3

import abc
  
  
class AbstractClass(metaclass=abc.ABCMeta):
    @property
    @abc.abstractmethod
    def abstractName(self):
        pass
  
  
class ValidSubClass(AbstractClass):
    @property
    def abstractName(self):
        return 'Abstract 1'
  
  
vc = ValidSubClass()
print(vc.abstractName)

Producción:

Abstract 1

Clases abstractas incorporadas

La biblioteca estándar de Python 3 proporciona algunas clases abstractas integradas para métodos abstractos y no abstractos. Estos incluyen secuencia, secuencia mutable, iterable, etc. A menudo sirve como una alternativa a la creación de subclases de una clase de Python integrada. Por ejemplo, la subclasificación de MutableSequence puede sustituir la subclasificación de list o str. El propósito principal de usar la clase Abstract es que le permite considerar un tipo común de colección en lugar de codificar para cada tipo de colección. Aquí discutiremos ABC de método único y ABC de colección alternativa.

  • ABC de método único
  • ABC de colección alternativa

ABC de método único

Python tiene cinco clases base abstractas. Son los siguientes:

  • Llamable (__call__)
  • Contenedor (__contiene__)
  • Hashable (__hash__)
  • Iterable (__iter__)
  • Tamaño (__len__)

Estas clases base abstractas contienen un método abstracto cada una. Consideremos un ejemplo del método __len__.

Python3

from collections.abc import Sized
  
  
class SingleMethod(object):
    def __len__(self):
        return 10
  
  
print(issubclass(SingleMethod, Sized))

Producción:

True

Cualquier clase que tenga el método apropiado se considera como la subclase de la clase base abstracta. De las cinco clases base abstractas anteriores, el iterador es ligeramente diferente. Proporciona una implementación para __iter__ y agrega un método abstracto llamado __next__ .

ABC de colección alternativa

Los ABC de colección alternativa son clases base abstractas integradas que identifican subclases, que tienen propósitos similares. Se pueden dividir en tres categorías. Repasemos uno por uno.

  • Secuencia y Secuencia mutable : Secuencia y Secuencia mutable son clases base abstractas que generalmente se comportan como tuplas o listas. Una clase base abstracta de secuencia requiere __getitem__ y __len__, mientras que una secuencia mutable necesita __setitem__ y __getitem__ .
  • Mapeo : el mapeo viene con un mapeo mutable, que es principalmente para objetos similares a diccionarios
  • Conjunto : el conjunto viene con un conjunto mutable que está destinado a colecciones desordenadas.

Resumen

El propósito clave de la clase abstracta es verificar si un objeto se ajusta a un protocolo particular. Es una clase valiosa para probar ciertos atributos de una clase o para probar la clase misma. Sin embargo, hay muchas otras cosas que la clase abstracta no verifica. Algunos de ellos son firmas, tipo de devolución, etc. Otra ventaja es que proporciona una forma flexible para que los desarrolladores prueben tipos comunes de colecciones.  

Publicación traducida automáticamente

Artículo escrito por SonuGeorge y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *