Capítulo 1. El modelo de datos de Python

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

El sentido que tiene Guido de la estética del diseño de lenguajes es asombroso. He conocido a muchos buenos diseñadores de lenguajes capaces de construir lenguajes teóricamente bellos que nadie utilizaría jamás, pero Guido es una de esas raras personas capaces de construir un lenguaje que es sólo un poco menos bello teóricamente, pero con el que da gusto escribir programas.

Jim Hugunin, creador de Jython, cocreador de AspectJ y arquitecto del DLR de .Net1

Una de las mejores cualidades de Python es su consistencia. Después de trabajar con Python durante un tiempo, eres capaz de empezar a hacer conjeturas informadas y correctas sobre características que son nuevas para ti.

Sin embargo, si aprendiste otro lenguaje orientado a objetos antes que Python, puede que te resulte extraño utilizar len(collection) en lugar de collection.len(). Esta aparente rareza es la punta de un iceberg que, bien entendido, es la clave de todo lo que llamamos Python. El iceberg se llama Modelo de Datos de Python, y es la API que utilizamos para que nuestros propios objetos funcionen bien con lascaracterísticas más idiomáticas del lenguaje.

Puedes pensar en el modelo de datos como una descripción de Python como marco de trabajo. Formaliza las interfaces de los bloques de construcción del propio lenguaje, como secuencias, funciones, iteradores, coroutines, clases, gestores de contexto, etc.

Cuando utilizamos un framework, pasamos mucho tiempo codificando métodos que son llamados por el framework. Lo mismo ocurre cuando aprovechamos el Modelo de Datos de Python para construir nuevas clases. El intérprete de Python invoca los métodos especiales para realizar operaciones básicas con objetos, a menudo provocadas por una sintaxis especial. Los nombres de los métodos especiales se escriben siempre con doble guión bajo inicial y final. Por ejemplo, la sintaxis obj[key] está soportada por el método especial __getitem__. Para evaluar my_collection[key], el intérprete llama a my_collection.__getitem__(key).

Implementamos métodos especiales cuando queremos que nuestros objetos admitan e interactúen con construcciones fundamentales del lenguaje, como:

  • Colecciones

  • Acceso a atributos

  • Iteración (incluida la iteración asíncrona mediante async for)

  • Sobrecarga de operadores

  • Invocación de funciones y métodos

  • Representación y formateo de cadenas

  • Programación asíncrona con await

  • Creación y destrucción de objetos

  • Contextos gestionados mediante las sentencias with o async with

Magia y Dunder

El término método mágico es argot para método especial, pero ¿cómo hablamos de un método específico como __getitem__? Aprendí a decir "dunder-getitem" del autor y profesor Steve Holden. "Dunder" es un atajo para "doble guión bajo antes y después". Por eso los métodos especiales también se conocen como métodos dunder. El capítulo "Análisis Léxico"de La Referencia del Lenguaje Python advierte que "Cualquier uso de los nombres __*__, en cualquier contexto, que no siga el uso explícitamente documentado, está sujeto a rotura sin previo aviso."

Novedades de este capítulo

Este capítulo de ha sufrido pocos cambios respecto a la primera edición, ya que se trata de una introducción al Modelo de Datos de Python, que es bastante estable. Los cambios más significativos son:

Además, aquí y en toda esta segunda edición he adoptado la sintaxis de cadena f introducida en Python 3.6, que es más legible y a menudo más cómoda que las antiguas notaciones de formato de cadena: el método str.format() y el operador %.

Consejo

Una razón para seguir utilizando my_fmt.format() es cuando la definición de my_fmt debe estar en un lugar del código distinto de donde debe producirse la operación de formateo. Por ejemplo, cuando my_fmt tiene varias líneas y es mejor definirla en una constante, o cuando debe proceder de un archivo de configuración, o de la base de datos. Ésas son necesidades reales, pero no ocurren muy a menudo.

Una baraja pitónica

El ejemplo 1-1 es sencillo, pero demuestra la potencia de implementar sólo dos métodos especiales, __getitem__ y __len__.

Ejemplo 1-1. Una baraja como secuencia de naipes
import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

La primera cosa que hay que observar es el uso de collections.namedtuplepara construir una clase sencilla que represente cartas individuales. Utilizamos namedtuple para construir clases de objetos que son sólo conjuntos de atributos sin métodos personalizados, como un registro de base de datos. En el ejemplo, lo utilizamos para proporcionar una bonita representación de las cartas de la baraja, como se muestra en la sesión de consola:

>>> beer_card = Card('7', 'diamonds')
>>> beer_card
Card(rank='7', suit='diamonds')

Pero lo importante de este ejemplo es la clase FrenchDeck. Es corta, pero tiene mucha fuerza. En primer lugar, como cualquier colección estándar de Python, una baraja responde a la función len() devolviendo el número de cartas que contiene:

>>> deck = FrenchDeck()
>>> len(deck)
52

Leer cartas concretas de la baraja -por ejemplo, la primera o la última- es fácil, gracias al método __getitem__:

>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')

¿Deberíamos crear un método para elegir una carta al azar? No es necesario. Python ya tiene una función para obtener un elemento aleatorio de una secuencia: random.choice. Podemos utilizarla en una instancia de baraja:

>>> from random import choice
>>> choice(deck)
Card(rank='3', suit='hearts')
>>> choice(deck)
Card(rank='K', suit='spades')
>>> choice(deck)
Card(rank='2', suit='clubs')

Acabamos de ver en dos ventajas de utilizar métodos especiales para aprovechar el Modelo de Datos de Python:

  • Los usuarios de tus clases no tienen que memorizar nombres arbitrarios de métodos para operaciones estándar. ("¿Cómo obtener el número de elementos? ¿Es .size(), .length(), o qué?")

  • Es más fácil beneficiarse de la rica biblioteca estándar de Python y evitar reinventar la rueda, como la función random.choice.

Pero la cosa mejora.

Como nuestro __getitem__ delega en el operador [] de self._cards, nuestra baraja admite automáticamente el troceado. Así es como miramos las tres cartas superiores de una baraja nueva, y luego elegimos sólo los ases empezando en el índice 12 y saltándonos 13 cartas cada vez:

>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),
Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]

Sólo con implementar el método especial __getitem__, nuestra baraja también es iterable:

>>> for card in deck:  # doctest: +ELLIPSIS
...   print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...

También podemos iterar sobre el mazo a la inversa:

>>> for card in reversed(deck):  # doctest: +ELLIPSIS
...   print(card)
Card(rank='A', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='Q', suit='hearts')
...

Elipsis en doctests

Siempre que fue posible , extraje los listados de la consola Python de este libro dedoctest Cuando la salida era demasiado larga, la parte elidida se marcaba con una elipsis (...), como en la última línea del código anterior. En esos casos, utilicé la directiva # doctest: +ELLIPSIS para superar el doctest. Si pruebas estos ejemplos en la consola interactiva, puedes omitir los comentarios del doctest.

La iteración suele ser implícita. Si una colección no tiene un método __contains__ , el operador in realiza un barrido secuencial. Un ejemplo: in funciona con nuestra clase FrenchDeck porque es iterable. Compruébalo:

>>> Card('Q', 'hearts') in deck
True
>>> Card('7', 'beasts') in deck
False

¿Qué tal la clasificación? Un sistema común de clasificación de cartas es por rango (siendo los ases los más altos), y luego por palo en el orden de picas (el más alto), corazones, diamantes y tréboles (el más bajo). Aquí tienes una función que clasifica las cartas según esa regla, devolviendo 0 para el 2 de tréboles y 51 para el as de picas:

suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)

def spades_high(card):
    rank_value = FrenchDeck.ranks.index(card.rank)
    return rank_value * len(suit_values) + suit_values[card.suit]

Teniendo en cuenta spades_high, ahora podemos enumerar nuestra baraja en orden creciente:

>>> for card in sorted(deck, key=spades_high):  # doctest: +ELLIPSIS
...      print(card)
Card(rank='2', suit='clubs')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='hearts')
... (46 cards omitted)
Card(rank='A', suit='diamonds')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')

Aunque FrenchDeck hereda implícitamente de la clase object, la mayor parte de su funcionalidad no se hereda, sino que proviene de aprovechar el modelo de datos y la composición. Al implementar los métodos especiales __len__ y __getitem__, nuestra FrenchDeck se comporta como una secuencia estándar de Python, lo que le permite beneficiarse de las características principales del lenguaje (por ejemplo, la iteración y el troceado) y de la biblioteca estándar, como muestran los ejemplos que utilizan random.choice,reversedsortedGracias a la composición, las implementaciones de __len__ y __getitem__ pueden delegar todo el trabajo en un objeto list,self._cards.

¿Qué te parece barajar?

Tal y como se ha implementado hasta ahora, un FrenchDeck no se puede barajar porque es inmutable: las cartas y sus posiciones no se pueden cambiar, salvo violando la encapsulación y manejando directamente el atributo _cards. En el Capítulo 13, lo arreglaremos añadiendo un método __setitem__ de una sola línea.

Cómo se utilizan los métodos especiales

La primera cosa que debes saber sobre los métodos especiales es que están pensados para ser llamados por el intérprete de Python, y no por ti. No escribes my_object.__len__(). Escribes len(my_object) y, si my_object es una instancia de una clase definida por el usuario, entonces Python llama al método __len__ que has implementado.

Pero el intérprete toma un atajo cuando trata con tipos incorporados como list, str, bytearray, o extensiones como las matrices de NumPy. Las colecciones de tamaño variable de Python escritas en C incluyen una estructura2 llamado PyVarObject, que tiene un campo ob_size que contiene el número de elementos de la colección. Por tanto, si my_object es una instancia de uno de esos constructores, entonces len(my_object) recupera el valor del campo ob_size, y esto es mucho más rápido que llamar a un método.

La mayoría de las veces, la llamada al método especial es implícita. Por ejemplo, la sentencia for i in x: en realidad provoca la invocación de iter(x), que a su vez puede llamar a x.__iter__() si está disponible, o utilizar x.__getitem__(), como en el ejemplo FrenchDeck.

Normalmente, tu código no debería tener muchas llamadas directas a métodos especiales. A menos que estés haciendo mucha metaprogramación, deberías implementar métodos especiales más a menudo que invocarlos explícitamente. El único método especial al que el código de usuario llama directamente con frecuencia es __init__para invocar el inicializador de la superclase en tu propia implementación __init__.

Si necesitas invocar un método especial, suele ser mejor llamar a la función incorporada correspondiente (por ejemplo, len, iter, str, etc.). Estas funciones incorporadas llaman al método especial correspondiente, pero a menudo proporcionan otros servicios y -para los tipos incorporados- son más rápidas que las llamadas a métodos. Consulta, por ejemplo, "Utilizar iter con un Callable" en el capítulo 17.

En los próximos apartados, veremos algunos de los usos más importantes de los métodos especiales:

  • Emular tipos numéricos

  • Representación en cadena de los objetos

  • Valor booleano de un objeto

  • Implantar colecciones

Emular tipos numéricos

Varios métodos especiales de permiten que los objetos de usuario respondan a operadores como +. Lo veremos con más detalle en el capítulo 16, pero aquí nuestro objetivo es ilustrar mejor el uso de los métodos especiales mediante otro ejemplo sencillo.

En implementaremos una clase para representar vectores bidimensionales, es decir, vectores euclidianos como los que se utilizan en matemáticas y física (véase la Figura 1-1).

Consejo

El tipo incorporado complex puede utilizarse para representar vectores bidimensionales, pero nuestra clase puede ampliarse para representar vectores n-dimensionales. Lo haremos en el Capítulo 17.

2D vectors
Figura 1-1. Ejemplo de suma de vectores bidimensionales; Vector(2, 4) + Vector(2, 1) da como resultado Vector(4, 5).

Empezaremos a diseñar la API de una clase de este tipo escribiendo una sesión de consola simulada que podremos utilizar más adelante como doctest. El siguiente fragmento de código prueba la suma de vectores que se muestra en la Figura 1-1:

>>> v1 = Vector(2, 4)
>>> v2 = Vector(2, 1)
>>> v1 + v2
Vector(4, 5)

Observa en cómo el operador + da como resultado un nuevo Vector, que se muestra en un formato amigable en la consola.

La función incorporada abs devuelve el valor absoluto de enteros y flotantes, y la magnitud de complex números, por lo que, para ser coherentes, nuestra API también utiliza abs para calcular la magnitud de un vector:

>>> v = Vector(3, 4)
>>> abs(v)
5.0

Nosotros también podemos implementar el operador * para realizar multiplicaciones escalares (es decir, multiplicar un vector por un número para obtener un nuevo vector con la misma dirección y una magnitudmultiplicada ):

>>> v * 3
Vector(9, 12)
>>> abs(v * 3)
15.0

El ejemplo 1-2 es una clase Vector que implementa las operaciones que acabamos de describir, mediante el uso de los métodos especiales __repr__ , __abs__, __add__, y __mul__.

Ejemplo 1-2. Una clase vectorial bidimensional simple
"""
vector2d.py: a simplistic class demonstrating some special methods

It is simplistic for didactic reasons. It lacks proper error handling,
especially in the ``__add__`` and ``__mul__`` methods.

This example is greatly expanded later in the book.

Addition::

    >>> v1 = Vector(2, 4)
    >>> v2 = Vector(2, 1)
    >>> v1 + v2
    Vector(4, 5)

Absolute value::

    >>> v = Vector(3, 4)
    >>> abs(v)
    5.0

Scalar multiplication::

    >>> v * 3
    Vector(9, 12)
    >>> abs(v * 3)
    15.0

"""


import math

class Vector:

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Vector({self.x!r}, {self.y!r})'

    def __abs__(self):
        return math.hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

Hemos implementado cinco métodos especiales, además del conocido __init__. Observa que ninguno de ellos se llama directamente dentro de la clase o en el uso típico de la clase ilustrado por los doctests. Como ya hemos dicho, el intérprete de Python es el único llamador frecuente de la mayoría de los métodos especiales.

El Ejemplo 1-2 implementa dos operadores: + y *, para mostrar el uso básico de __add__ y __mul__. En ambos casos, los métodos crean y devuelven una nueva instancia de Vector, y no modifican ninguno de los operandos -self o other se limitan a leerlos. Éste es el comportamiento esperado de los operadores infijos: crear nuevos objetos y no tocar sus operandos. Tendré mucho más que decir al respecto en el Capítulo 16.

Advertencia

Tal como está implementado, el Ejemplo 1-2 permite multiplicar un Vector por un número, pero no un número por un Vector, lo que viola la propiedad conmutativa de la multiplicación escalar. Lo arreglaremos con el método especial __rmul__ en el Capítulo 16.

En los apartados siguientes, hablaremos de otros métodos especiales en Vector.

Representación de cadenas

El método especial __repr__ es llamado por el repr incorporado para obtener la representación en cadena del objeto para su inspección. Sin un __repr__ personalizado, la consola de Python mostraría una instancia Vector <Vector object at 0x10e100070> .

La consola interactiva y el depurador llaman a repr sobre los resultados de las expresiones evaluadas, al igual que el marcador de posición %r en el formateo clásico con el operador %, y el !r campo de conversión en la nueva sintaxis de cadena de formato utilizada en f-cadenas el método str.format.

Observa que la cadena f de nuestro __repr__ utiliza !r para obtener la representación estándar de los atributos que se van a mostrar. Esto es una buena práctica, porque muestra la diferencia crucial entre Vector(1, 2) y Vector('1', '2')-este último no funcionaría en el contexto de este ejemplo, porque los argumentos del constructor deben ser números, no str.

La cadena devuelta por __repr__ debe ser inequívoca y, a ser posible, coincidir con el código fuente necesario para volver a crear el objeto representado. Por eso, nuestra representación Vector se parece a llamar al constructor de la clase (por ejemplo, Vector(3, 4)).

En el contraste , __str__ es llamado por el incorporado str() y utilizado implícitamente por la función print. Debe devolver una cadena adecuada para mostrarla a los usuarios finales.

A veces, la misma cadena devuelta por __repr__ es fácil de usar, y no necesitas codificar __str__ porque la implementación heredada de la clase object llama a __repr__ como alternativa.El ejemplo 5-2 es uno de los varios ejemplos de este libro con un __str__ personalizado.

Consejo

Los programadores con experiencia previa en lenguajes con un método toString tienden a implementar __str__ y no __repr__. Si sólo implementas uno de estos métodos especiales en Python, elige __repr__.

"¿Cuál es la diferencia entre __str__ y __repr__ en Python?" es una pregunta de Stack Overflow con excelentes contribuciones de los pythonistas Alex Martelli y Martijn Pieters.

Valor booleano de un tipo personalizado

Aunque Python tiene un tipo bool, acepta cualquier objeto en un contexto booleano, como la expresión que controla una sentencia if o while, o como operandos de and, or y not. Para determinar si un valor x es verdadero o falso, Python aplica bool(x), que devuelve True o False.

Por defecto, las instancias de las clases definidas por el usuario se consideran verdaderas, a menos que __bool__ o __len__ estén implementadas. Básicamente, bool(x) llama a x.__bool__() y utiliza el resultado. Si __bool__ no está implementada, Python intenta invocar a x.__len__(), y si eso devuelve cero, bool devuelve False. De lo contrario bool devuelve True.

Nuestra implementación de __bool__ es conceptualmente sencilla: devuelve False si la magnitud del vector es cero, True en caso contrario. Convertimos la magnitud a un booleano utilizando bool(abs(self)) porque se espera que __bool__ devuelva un booleano. Fuera de los métodos de __bool__, rara vez es necesario llamar explícitamente a bool(), porque cualquier objeto puede utilizarse en un contexto booleano.

Observa cómo el método especial __bool__ permite que tus objetos sigan las reglas de comprobación del valor de verdad definidas enel capítulo "Tipos incorporados"de la documentación La biblioteca estándar de Python.

Nota

Una aplicación más rápida de Vector.__bool__ es ésta:

    def __bool__(self):
        return bool(self.x or self.y)

Esto es más difícil de leer, pero evita el viaje a través de abs, __abs__, los cuadrados y la raíz cuadrada. La conversión explícita a bool es necesaria porque __bool__ debe devolver un booleano, yor devuelve cualquiera de los operandos tal cual: x or y evalúa a x si es verdadero, de lo contrario el resultado es y, sea lo que sea.

API de recogida

La Figura 1-2 documenta en las interfaces de los tipos de colección esenciales del lenguaje. Todas las clases del diagrama sonclases base abstractas (ABC). Las ABC y el módulo collections.abc se tratan en el Capítulo 13. El objetivo de esta breve sección es ofrecer una visión panorámica de las interfaces de colección más importantes de Python, mostrando cómo se construyen a partir de métodos especiales.

UML class diagram with all superclasses and some subclasses of `abc.Collection`
Figura 1-2. Diagrama de clases UML con los tipos de colecciones fundamentales. Los nombres de métodos en cursiva son abstractos, por lo que deben ser implementados por subclases concretas como list y dict. Los métodos restantes tienen implementaciones concretas, por lo que las subclases pueden heredarlos.

El ABC Collection (nuevo en Python 3.6) unifica las tres interfaces esenciales que toda colección debeimplementar:

  • Iterable para que admita for, desempaquetado y otras formas deiteración

  • Sized para soporta la función incorporada len

  • Container para apoyar al operador in

Python no exige que las clases concretas hereden realmente de ninguno de estos ABC. Cualquier clase que implemente __len__ satisface la interfaz Sized.

Tres especializaciones muy importantes de Collection son:

  • Sequenceformalizando la interfaz de componentes como list y str

  • Mappingimplementado por dict, collections.defaultdict, etc.

  • Setla interfaz de los tipos incorporados set y frozenset

Sólo Sequence es Reversible, porque las secuencias admiten el orden arbitrario de sus contenidos, mientras que las correspondencias y los conjuntos no.

Nota

Desde Python 3.7, el tipo dict es oficialmente "ordenado", pero eso sólo significa que se conserva el orden de inserción de las claves. No puedesreordenar las claves de un dict como quieras.

Todos los métodos especiales del ABC Set implementan operadores infijos. Por ejemplo,a & b calcula la intersección de los conjuntos a y b, y se implementa en el método especial __and__.

Los dos capítulos siguientes tratarán en detalle las secuencias, asignaciones y conjuntos de la biblioteca estándar.

Consideremos ahora las principales categorías de métodos especiales definidos en el Modelo de Datos de Python.

Visión general de los métodos especiales

En el capítulo "Modelo de datos" de La Referencia del Lenguaje Python se enumeran más de 80 nombres de métodos especiales. Más de la mitad de ellos implementan operadores aritméticos, bit a bit y de comparación. Como resumen de lo que hay disponible, consulta las tablas siguientes.

La Tabla 1-1muestra los nombres de los métodos especiales, excluyendo los utilizados para implementar operadores infijos o funciones matemáticas básicas como abs. La mayoría de estos métodos se tratarán a lo largo del libro, incluidas las incorporaciones más recientes: métodos especiales asíncronos como __anext__ (añadido en Python 3.5), y el gancho de personalización de clases, __init_subclass__ (de Python 3.6).

Tabla 1-1. Nombres de métodos especiales (operadores excluidos)
Categoría Nombres de los métodos

Representación cadena/bytes

__repr__ __str__ __format__ __bytes__ __fspath__

Conversión a número

__bool__ __complex__ __int__ __float__ __hash__ __index__

Emular colecciones

__len__ __getitem__ __setitem__ __delitem__ __contains__

Iteración

__iter__ __aiter__ __next__ __anext__ __reversed__

Ejecución invocable o coroutine

__call__ __await__

Gestión del contexto

__enter__ __exit__ __aexit__ __aenter__

Creación y destrucción de instancias

__new__ __init__ __del__

Gestión de atributos

__getattr__ __getattribute__ __setattr__ __delattr__ __dir__

Descriptores de atributos

__get__ __set__ __delete__ __set_name__

Clases base abstractas

__instancecheck__ __subclasscheck__

Metaprogramación de clases

__prepare__ __init_subclass__ __class_getitem__ __mro_entries__

Los operadores infijos y numéricos están soportados por los métodos especiales enumerados enla Tabla 1-2. Aquí los nombres más recientes son __matmul__, __rmatmul__, y __imatmul__, añadidos en Python 3.5 para soportar el uso de @ como operador infijo para la multiplicación de matrices, como veremos en el Capítulo 16.

Tabla 1-2. Nombres de métodos especiales y símbolos de operadores
Categoría de operador Símbolos Nombres de los métodos

Numérico unario

- + abs()

__neg__ __pos__ __abs__

Rica comparación

< <= == != > >=

__lt__ __le__ __eq__ __ne__ __gt__ __ge__

Aritmética

+ - * / // % @ divmod() round() ** pow()

__add__ __sub__ __mul__ __truediv__ __floordiv__ __mod__ __matmul__ __divmod__ __round__ __pow__

Aritmética inversa

(operadores aritméticos con operandos intercambiados)

__radd__ __rsub__ __rmul__ __rtruediv__ __rfloordiv__ __rmod__ __rmatmul__ __rdivmod__ __rpow__

Aritmética de asignación aumentada

+= -= *= /= //= %= @= **=

__iadd__ __isub__ __imul__ __itruediv__ __ifloordiv__ __imod__ __imatmul__ __ipow__

Bitwise

& | ^ << >> ~

__and__ __or__ __xor__ __lshift__ __rshift__ __invert__

Bit a bit invertido

(operadores bit a bit con operandos intercambiados)

__rand__ __ror__ __rxor__ __rlshift__ __rrshift__

Asignación aumentada bit a bit

&= |= ^= <<= >>=

__iand__ __ior__ __ixor__ __ilshift__ __irshift__

Nota

Python llama a un método especial de operador inverso en el segundo operando cuando no se puede utilizar el método especial correspondiente en el primer operando. Las asignaciones aumentadas son atajos que combinan un operador infijo con una asignación de variable, por ejemplo, a += b.

El capítulo 16 explica detalladamente los operadores inversos y la asignación aumentada.

Por qué el len no es un método

Yo hice esta pregunta al desarrollador del núcleo Raymond Hettinger en 2013, y la clave de su respuesta fue una cita de "El Zen de Python":"En "Cómo se utilizan los métodos especiales", describí cómo len(x) se ejecuta muy rápido cuando x es una instancia de un tipo incorporado. No se llama a ningún método para los objetos incorporados de CPython: la longitud simplemente se lee de un campo de una estructura C. Obtener el número de elementos de una colección es una operación común y debe funcionar eficientemente para tipos tan básicos y diversos como str, list, memoryview, etc.

En otras palabras, len no se llama como método porque recibe un tratamiento especial como parte del Modelo de Datos de Python, al igual que abs. Pero gracias al método especial __len__, también puedes hacer que len funcione con tus propios objetos personalizados. Se trata de un compromiso justo entre la necesidad de objetos incorporados eficientes y la coherencia del lenguaje. También de "El Zen de Python": "Los casos especiales no son tan especiales como para romper las reglas".

Nota

Si piensas en abs y len como operadores unarios, puede que te sientas más inclinado a perdonar su aspecto funcional, en contraposición a la sintaxis de llamada a métodos que cabría esperar en un lenguaje orientado a objetos. De hecho, el lenguaje ABC -un antepasado directo de Python que fue pionero en muchas de sus características- tenía un operador # que era el equivalente a len (escribirías #s). Cuando se utilizaba como operador infijo, escrito x#s, contaba las apariciones de x en s, que en Python obtienes como s.count(x), para cualquier secuencia s.

Resumen del capítulo

Mediante la implementación de métodos especiales en, tus objetos pueden comportarse como los tipos incorporados, permitiendo el estilo de codificación expresivo que la comunidad considera pitónico.

Un requisito básico para un objeto Python es proporcionar representaciones de cadena utilizables de sí mismo, una utilizada para la depuración y el registro, y otra para la presentación a los usuarios finales. Por eso existen los métodos especiales __repr__ y __str__ en el modelo de datos.

Emular secuencias, como se muestra con el ejemplo de FrenchDeck, es uno de los usos más comunes de los métodos especiales. Por ejemplo, las bibliotecas de bases de datos suelen devolver resultados de consultas envueltos en colecciones similares a secuencias. Aprovechar al máximo los tipos de secuencias existentes es el tema del Capítulo 2. Implementar tus propias secuencias se tratará en el Capítulo 12, cuando creemos una extensión multidimensional de la clase Vector.

Gracias a la sobrecarga de operadores, Python ofrece una rica selección de tipos numéricos, desde los incorporados a decimal.Decimal y fractions.Fraction, todos ellos compatibles con operadores aritméticos infijos. Las bibliotecas de ciencia de datos NumPy admiten operadores infijoscon matrices y tensores. La implementación de operadores -incluidos los operadores inversos y la asignación aumentada- se mostrará en el capítulo 16 mediante mejoras del ejemploVector ejemplo.

El uso y la implementación de la mayoría de los restantes métodos especiales del Modelo de Datos de Python se tratan a lo largo de este libro.

Otras lecturas

El capítulo "Modelo de datos" de La Referencia del Lenguaje Python es la fuente canónica para el tema de este capítulo y de gran parte de este libro.

Python in a Nutshell, 3ª ed. de Alex Martelli, Anna Ravenscroft y Steve Holden (O'Reilly) tiene una excelente cobertura del modelo de datos. Su descripción de la mecánica del acceso a atributos es la más fidedigna que he visto aparte del códigofuente en C de CPython. Martelli es también un prolífico colaborador de Stack Overflow,con más de 6.200 respuestas publicadas. Consulta su perfil de usuario en Stack Overflow.

David Beazley tiene dos libros que tratan en detalle el modelo de datos en el contexto de Python 3: Python Essential Reference, 4ª ed. (Addison-Wesley), y Python Cookbook, 3ª ed. (O'Reilly), en coautoría con Brian K. Jones.

El arte del protocolode meta objetos (MIT Press), de Gregor Kiczales, Jim des Rivieres y Daniel G. Bobrow, explica el concepto de protocolo de metaobjetos, del que el Modelo de Datos de Python es un ejemplo.

1 "Historia de Jython", escrito como prólogo a Jython Essentials por Samuele Pedroni y Noel Rappin (O'Reilly).

2 Una estructura C es un tipo de registro con campos con nombre.

Get Python fluido, 2ª edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.