Subido por Julia visual

Actualización del libro a Python 3.10 contenido adicional 2

Anuncio
Actualización del libro
PYTHON A FONDO
a la versión 3.10
Óscar Ramírez Jiménez
Extracto de Python a fondo
Primera edición, 2021
Primera reimpresión, 2021
Segunda reimpresión actualizada, 2022
© 2021 Óscar Ramírez Jiménez
© 2021 MARCOMBO, S. L.
www.marcombo.com
Diseño de cubierta: ENEDENÚ DISEÑO GRÁFICO
Revisor técnico: Ferran Fábregas
Corrección: Haizea Beitia y Manel Fernández
Maquetación: D. Márquez
Directora de producción: M.a Rosa Castillo
«Cualquier forma de reproducción, distribución, comunicación pública o transformación de esta
obra solo puede ser realizada con la autorización de sus titulares, salvo excepción prevista por la ley.
Diríjase a CEDRO (Centro Español de Derechos Reprográficos, www.cedro.org) si necesita fotocopiar
algún fragmento de esta obra».
ISBN: 978-84-267-3227-9
D.L.: B 19897-2020
Impreso en Servicepoint
Printed in Spain
Python a fondo
• Versión 3.8 (octubre del 2019): introducción del operador walrus,
se añaden los parámetros solo-posicionales usando /en las funciones
y el soporte de = para los f-strings, para autodocumentar expresiones
y ayudar a depurar.
• Versión 3.9 (octubre del 2020): se añade el paquete zoneinfo para
facilitar el uso de zonas horarias en fechas, se añade el operador de
unión (|) para diccionarios, se permite el uso de expresiones en decoradores, se añaden los métodos removesuffix y removeprefix para
cadenas de caracteres, se permite usar tipos del builting para definir
hints sin necesidad de importar la librería y se añade Annotated a
typing para mejorar la integración de ambas, entre otros muchos
cambios. Cabe destacar que en esta versión se han borrado muchas
funciones que estaban presentes por retrocompatibilidad con la
versión 2, y que en las siguientes versiones se borrarán más.
• Versión 3.10 (octubre del 2021): Se mejoran considerablemente
algunos mensajes de error siendo mucho más descriptivos y ofreciendo alternativas a errores comunes, se añade una nueva sentencia
para el control de flujo basado en patrones de coincidencia llamada
Structural Pattern Matching, se aplican multiples mejoras en el
sistema de sugerencias de tipado, además de añadir nuevas funcionalidades y optimizaciones en los tipos str, bytes y bytesarray
entre otros cambios.
Para más información sobre cada una de las versiones y los cambios entre
una y otra es recomendable revisar con frecuencia la web oficial de lenguaje
de programación Python: https://www.python.org/doc/versions/.
1.2 CARACTERÍSTICAS PRINCIPALES
DE LOS LENGUAJES DE PROGRAMACIÓN
Los humanos nos comunicamos por medio de un lenguaje (mayoritariamente verbal). De forma similar, para comunicarnos con las máquinas hemos diseñado diferentes formas de comunicación denominadas lenguajes
de programación. Al igual que los lenguajes utilizados entre humanos, los
lenguajes de programación tienen diferentes características; están orientados a satisfacer las necesidades por las que han sido creados y los gustos
de sus creadores y desarrolladores principales. Las principales características por las que un lenguaje de programación se puede caracterizar son la
generación a la que pertenece, el nivel de abstracción del lenguaje, el tipo
de tipado de variables, los paradigmas de programación que soporta y el
propósito que tiene el lenguaje, como se verá en los próximos apartados.
4
Python 2ªED.indb 4
10/1/22 13:40
Capítulo 1 · Introducción al lenguaje Python
1.10.2 Instalación en Windows
Es altamente improbable que en un sistema Windows ya se encuentre instalada una versión de Python por defecto, pero aún más que sea la última
versión o la versión que se desee utilizar. Por tanto, en esta sección se expone cómo instalar Python en Windows.
La forma más rápida y efectiva es descargar el instalador de la página oficial
de Python: https://www.python.org/downloads/. Es importante tener en
cuenta que la versión del sistema operativo coincida con la del instalador,
32 o 64 bits.
Figura 1.4 Instalar Python en Windows.
Como se puede ver en la Figura 1.4, el instalador de Python es igual que cualquier instalador estándar de Windows, con la peculiaridad de que permite seleccionar dónde instalar la versión de Python. Es importante que se seleccione
la opción de añadir Python al PATH (Add Python 3.10 to PATH en la imagen),
dado que, así, en cualquier terminal de Windows se podrá ejecutar cualquier
programa Python llamando al intérprete como se muestra a continuación:
$ python <nombre_del_fichero.py>
Si se utiliza Windows 10, se puede instalar el subsistema de Windows para Linux (WSL), el cual
permite ejecutar una instalación de Ubuntu Linux en el sistema para disponer de herramientas
Unix (https://docs.microsoft.com/es-es/windows/wsl/). Otra alternativa interesante es el uso
de Cygwin (https://www.cygwin.com/) si WSL no está disponible en su sistema.
37
Python 2ªED.indb 37
10/1/22 13:40
Capítulo 1 · Introducción al lenguaje Python
Figura 1.5 Instalador gráfico de Python para Mac OS X.
Una vez terminado el proceso de instalación, se puede ver que en las aplicaciones instaladas hay dos nuevos programas, IDLE y Python Launcher.
IDLE es un editor de desarrollo de código Python e intérprete de desarrollo
que se estudiará en profundidad más adelante en este libro. Por otro lado,
Python Launcher es un lanzador de aplicaciones de Python. Es la aplicación
que se puede configurar para abrir por defecto cualquier archivo con extensión de python o, si simplemente se arrastra un fichero python hacia el
icono del launcher, este lo lanzará en el destino que esté configurado, por
defecto, en una consola.
1.11 DISTRIBUCIONES DE PYTHON
Existen distribuciones de Python que pretenden unir diferentes paquetes
de librerías comúnmente utilizados en un ámbito específico con la finalidad de facilitar la instalación de todos ellos a la vez, para así comenzar a
utilizar las herramientas lo antes posible sin dedicar tiempo a instalar cada
componente por separado.
Normalmente, estas distribuciones tienen más herramientas de las que un
principiante, o incluso un experto, necesitaría, pero así se intenta cubrir
el máximo número de casos de uso, aunque suponga tener que hacer una
distribución de mayor tamaño. A continuación, se muestran algunas de las
distribuciones de Python más conocidas.
39
Python 2ªED.indb 39
10/1/22 13:40
Python a fondo
por ejemplo, sumas, multiplicaciones o elevar un número a otro, tanto si
son números complejos como si es una combinación de complejos con
enteros o reales.
>>> complex('1+4j')
(1+4j)
>>> 5 + 2.34 + 5J - 12.4563
(-5.116300000000001+5j)
>>> 1j ** 2
(-1+0j)
>>> pow(3j, 2)
(-9+0j)
Las operaciones disponibles para este tipo de dato se pueden obtener haciendo uso de la función dir aplicada a una instancia de esta clase:
>>> dir(complex())
# similar a dir(4 + 5J) por ejemplo
['__abs__', '__add__', '__bool__', '__class__', '__
delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__getnewargs__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__', '__
lt__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__',
'__pow__', '__radd__', '__reduce__', '__reduce_ex__', '__
repr__', '__rmul__', '__rpow__', '__rsub__', '__rtruediv__',
'__setattr__', '__sizeof__', '__str__', '__sub__', '__
subclasshook__', '__truediv__', 'conjugate', 'imag', 'real']
# Python 3.10
• complex.real: devuelve la parte real del número complejo asociado.
• complex.imag: devuelve la parte imaginaria del número complejo
asociado.
• complex.conjugate(): devuelve el número complejo conjugado del
número asociado.
A continuación, se muestran ejemplos de uso de esas operaciones:
>>> x = 4 - 2j
>>> x.conjugate()
(4+2j)
>>> x.imag
-2.0
>>> x.real
4.0
100
Python 2ªED.indb 100
10/1/22 13:40
Capítulo 2 · Variables y tipos de datos
>>> a.hex('_', 2)
'5c78_3739_5c78_3230'
>>> b'ñ'
File "<stdin>", line 1
b'ñ'
^
SyntaxError: bytes can only contain ASCII literal characters
9 CONJUNTOS (SET Y FROZENSET)
En Python existe un tipo de dato básico que representa una colección
no ordenada de elementos únicos. Este tipo de dato se denomina set o
frozenset dependiendiendo de sus características.
Sus caractecterísticas principales son que puede contener cualquier tipo de
dato y mezclarlo sin problemas y que, además, se encarga de tener un, y solo
un, elemento igual. Por lo tanto, es de mucha utilidad en multitud de situaciones. La mayor restricción es que todos sus elementos deben ser hasheables.
Un objeto es hasheable automáticamente si nunca cambia durante su
tiempo de vida. La mayoría de los objetos inmutables son hasheables, y las
tuplas o los frozensets son hasheables si todos sus elementos los son.
Las listas son un tipo de dato que no es hasheable.
Técnicamente, si una clase implementa la función __hash__(), todos los
objetos de esa clase son hasheables (los detalles sobre clases e instancias se
verán más adelante en este libro).
La principal diferencia entre frozenset y set es que un tipo es mutable y
el otro no. Además, los frozenset, aparte de ser inmutables, son hasheables, mientras que los set, no.
Los constructores de estos tipos de datos tienen el mismo nombre que los
tipos en sí: frozenset y set. Se pueden instanciar con objetos si se añade
un argumento iterable al constructor, de lo contrario, se crearán como vacíos.
Alternativamente, para la creación de set se puede usar una versión simplificada que consta de un iterador rodeado de los caracteres '{' '}'.
>>> set(), frozenset()
# Crea un conjunto vacío
(set(), frozenset())
>>> set([1,2,3,4])
{1, 2, 3, 4}
>>> {1,2,3,4}
{1, 2, 3, 4}
163
Python 2ªED.indb 163
10/1/22 13:40
Python a fondo
centinela, será elevada una excepción del tipo StopIteration,
de lo contrario, devolverá el valor de la llamada.
• enumerate(iterable, start=0): devuelve un objeto enumerate, el cual es una tupla de dos elementos en la que el primero es
un entero que determina el índice (comenzando por start), y el
segundo elemento es el elemento en la posición del índice del iterable. El objeto usado como iterable puede ser tanto una secuencia
como un iterador o cualquier objeto que soporte iteraciones.
• any(iterable): devuelve True si alguno de los elementos del iterable es verdadero. Si el iterable está vacío, devuelve Falso.
• all(iterable): devuelve True si todos de los elementos del iterable
son verdaderos o si el iterable está vacío.
• map(function, iterable, …): esta función devuelve un iterador,
que irá aplicando la función que se le pasa como primer parámetro, a
cada elemento de iterable, y devolverá uno a uno los resultados.
Adicionalmente, se pueden añadir más iterables y hacer que cada
elemento de cada iterable se le pase como parámetro a la función de
forma paralela, hasta que alguno de los iterables se quede sin elementos. Por tanto, el número de elementos totales devueltos será igual a
la longitud del menor iterable.
• list([iterable]): crea una lista con los valores obtenidos tras iterar
un iterable (iterable) hasta llegar al final del mismo. También se
usa en iteradores con el mismo fin, dado que ayuda a no tener que
manejar las excepciones StopIterator.
• reversed(secuencia): devuelve un iterador en orden inverso desde
la secuencia que se le pasa como parámetro. El objeto puede no ser una
secuencia, pero debe implementar el método mágico __reversed__
o los del protocolo de las secuencias (__len__ y __getitem__).
• zip(*iterables, strict=False): genera un iterador como agregación de cada elemento en la misma posición de cada iterable que se le
pase como parámetro soportando mínimo 2 iterables y devolviendo
tuplas de tantos elementos como iterables se usen (uno por cada iterable). La longitud final del iterador será igual a la menor longitud de
todos los iterables. El parámetro strict se añadió en Python 3.10 y
si es True, comprueba si la longitud de ambos iterables es la misma,
de lo contrario eleva una excepción tipo ValueError.
− Nota: cuando se usa esta función es común utilizar el operador *,
el cual permite descomponer una secuencia de elementos en argumentos para una función o una asignación y así poder concatenar
varias funciones zip.
184
Python 2ªED.indb 184
10/1/22 13:40
Capítulo 3 · Fundamentos del lenguaje
>>> if x < 0:
...
print('Número negativo')
...
print('El número es cero')
...
print('Número positivo')
... elif x == 0:
... else:
...
Número positivo
Como se puede ver en el ejemplo, para comenzar a controlar el flujo se utiliza la cláusula if seguida de una expresión. Si se pretende comprobar otra
expresión, cuando la anterior o anteriores han resultado falsas, se utilizan
tantas clausulas elif como sean necesarias y al final de la comprobación
se añade opcionalmente una clausula else, que el programa seguirá ejecutando si todas las anteriores expresiones se evaluaron como falsas.
2.2 IMPLEMENTACIONES DE SWITCH CASE EN PYTHON
Un control de flujo muy utilizado en los lenguajes de programación es el
switch (o case dependiendo del lenguaje) con el que se pretende encauzar
la ejecución de código dependiendo del valor específico de una variable que
se utiliza como operador.
Python implementa una sentencia de control similar a partir de la versión
3.10, aunque si se utiliza una versión anterior, es muy simple de emular
utilizando sentencias if o un simple diccionario como se puede ver en los
siguientes ejemplos:
>>> tipo = 'coche'
>>> if tipo == 'coche':
...
ruedas = 4
...
ruedas = 2
...
ruedas = 6
...
ruedas = -1
... elif tipo == 'bicicleta':
... elif tipo == 'camión':
... else:
...
>>> ruedas
4
201
Python 2ªED.indb 201
10/1/22 13:40
Python a fondo
>>> tipo_a_ruedas = {'coche': 4, 'bicicleta': 2, 'camión':
6}
>>> ruedas2 = tipo_a_ruedas.get(tipo, -1)
>>> ruedas2
4
2.3 STRUCTURAL PATTERN MATCHING
En la versión 3.10 se introdujo la sentencia de control que permite hacer
comparaciones estructurales de patrones. Es una sentencia de control similar a la sentencia switch que existe en otros lenguajes de programación,
pero muchisimo más potente como se puede ver en esta sección.
La definición de esta sentencia formalmente es la siguiente:
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
Donde match y case son palabras reservadas, subject es la variable que
será utilizada para comparar, pattern_x son todos los patrones a evaluar,
action_x es cada una de las acciones a ejecutar y por defecto se puede
utilizar _ que será ejecutado si ninguno de los patrones anteriores coincide.
El ejemplo más simple es el control de ejecución dependiendo del valor que
tenga una variable como por ejemplo comprobar si una variable es exactamente un valor:
>>> def comprobar_nombre(nombre):
...
...
...
match nombre:
case 'Oscar':
print('Correcto!')
...
case 'El Pythonista':
...
case _:
...
print('Ha estado cerca!')
202
Python 2ªED.indb 202
10/1/22 13:40
Capítulo 3 · Fundamentos del lenguaje
print('No es correcto')
...
...
>>> comprobar_nombre('Juan')
No es correcto
>>> comprobar_nombre('El Pythonista')
Ha estado cerca!
>>> comprobar_nombre('Oscar')
Correcto!
Los patrones utilizados pueden tener sentencias if para comprobar algunas si la variable cumple alguna condición especial, pueden ser concatenados para intentar coincidir con varios patrones usando | o directamente
cumplir ciertos patrones estructurales como ser instancia de un tipo en
concreto (str en el siguiente ejemplo):
>>> from enum import Enum
>>> class Color(Enum):
...
...
...
...
Blue = '#0000FF'
Red = '#FF0000'
Yellow = '#FFFF00'
>>> def temperatura_color(color):
...
...
match color:
...
case 'gris':
return 'Neutro'
...
case Color.Yellow | Color.Red:
...
case Color.Blue:
...
case str(x) if int(x[1:], base=16) > 256:
...
...
...
...
return 'Cálido'
return 'Frío'
return 'Cálido'
>>> temperatura_color('gris')
Neutro
>>> print(temperatura_color(Color.Blue))
Frío
>>> temperatura_color('#F0F022')
'Cálido'
203
Python 2ªED.indb 203
10/1/22 13:40
Python a fondo
El ejemplo más simple es el control de ejecución dependiendo del valor que
tenga una variable como por ejemplo comprobar si una variable es exactamente un valor:
Para más información y casos de uso, se recomienda revisar el tutorial oficial de PEP 636 https://www.python.org/dev/peps/pep-0636/
2.4 SENTENCIA if TERNARIA
En Python existe una opción simplificada para utilizar la sentencia if cuando
se desea usar en una sola sentencia, denominada ternaria, que se utiliza solamente en casos en los que queda claramente expresada la lógica inherente:
>>> edad = 55
>>> categoria = 'Cadete' if edad < 15 else 'Adulto'
>>> categoria
'Adulto'
Como se puede ver en el ejemplo anterior, la forma ternaria solo necesita de
las sentencias if y else en la parte derecha de una asignación. Se aconseja
usarla solo cuando la lógica a utilizar quede clara y de forma concisa. Como
forma general se puede definir de la siguiente manera:
V = A if expression == True else B
3 FLUJO DE EJECUCIÓN CON BUCLES
A menudo, el flujo de un algoritmo se mantiene realizando operaciones
hasta que una expresión lógica cambie de valor u ocurra algún acontecimiento esperado. Para este tipo de ejecuciones se implementan los bucles,
que son trozos de código que deben ser ejecutados hasta que una expresión
determinada ocurra.
3.1 ANALIZANDO BUCLES while
El primer ejemplo de creación de bucles que se va a estudiar en esta sección es la sentencia while ("mientras" en inglés). Esta sentencia permite
permanecer iterando sobre un bloque del código continuamente "mientras"
una expresión sea verdadera. La sintaxis básica es la siguiente:
while_stmt ::=
"while" expression ":" suite
["else" ":" suite]
204
Python 2ªED.indb 204
10/1/22 13:40
Python a fondo
El siguiente código está guardado en un fichero y se ejecuta desde consola
para ver cómo se representan los errores cuando se utilizan ficheros:
def capitalizar(elem):
return elem.capitalize()
def formatea(elem):
limpio = elem.trim()
capitalizado = capitalizar(limpio)
return capitalizado
def formateador(elementos):
resultado = []
for elem in elementos:
resultado.append(formatea(elem))
if __name__ == '__main__':
print(formateador(' Jose '))
print(formateador(2))
# python excepciones.py
Traceback (most recent call last):
File "/ruta/hasta/archivo/excepciones.py", line 34, in
<module>
print(formateador(' Jose '))
File ""/ruta/hasta/archivo/excepciones.py", line 30, in
formateador
resultado.append(formatea(elem))
File "/ruta/hasta/archivo/excepciones.py", line 22, in formatea
limpio = elem.trim()
AttributeError: 'str' object has no attribute 'trim'. Did
you mean: 'strip'?
En este ejemplo se puede ver cómo es una traza de error cuando ocurre en
ficheros.
Para obtener un objeto Traceback se puede hacer uso de sys.exc_
info() dentro del contexto de la excepción que se ha elevado, como en el
siguiente ejemplo:
256
Python 2ªED.indb 256
10/1/22 13:40
Python a fondo
>>> entero = 2
>>> entero.upper()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'upper'
>>> cadena = 'Parque', 'florido'
>>> cadena.lower()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'lower'
>>> cadena
('Parque', 'florido')
>>> info = dict(color='Verde', tipo='Coche')
>>> modelo = info.get('modelo')
>>> modelo.upper()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'upper'
>>> info.items = 'Pelota'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object attribute 'items' is read-only
>>> # desde 3.10
>>> 'hola'.trim
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'trim'. Did
you mean: 'strip'?
>>> info.item
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'item'. Did
you mean: 'items'?
Como se puede ver en los ejemplos, es muy fácil identificar la causa del error,
dado que en el mensaje de la excepción aparece el tipo del objeto sobre el que
se ha intentado llamar al atributo y el atributo inexistente en el objeto.
260
Python 2ªED.indb 260
10/1/22 13:40
Python a fondo
NameError
Esta excepción se eleva cuando un nombre local o global no es encontrado.
Por tanto, suele ocurrir cuando no se ha inicializado una variable antes de
ser usada o cuando se escribe de forma errónea:
>>> mi_variable
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'mi_variable' is not defined
>>> color = 'Amarillo'
>>> print(clor)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'clor' is not defined. Did you mean:
'color'?
>>> animal = 'gato'
>>> animall
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'animall' is not defined. Did you mean:
'animal'?
Como se puede ver en el ejemplo, cuando se define la función foo no se
eleva la excepción, sino que ocurre cuando se intenta usar la función.
SyntaxError
Esta excepción se eleva cuando hay un error de sintaxis y el parseador de
código de Python lo encuentra. Las causas pueden ser múltiples, desde
un identificador de variable no apropiado hasta una definición de función
errónea, pasando por otros muchos casos:
>>> a-4 = 4
File "<stdin>", line 1
a-4 = 4
^^^
SyntaxError: cannot assign to expression here. Maybe you
meant '==' instead of '='?
>>> def foo
File "<stdin>", line 1
264
Python 2ªED.indb 264
10/1/22 13:40
Capítulo 3 · Fundamentos del lenguaje
def foo
^
SyntaxError: invalid syntax
>>> mi_dict = {
...
'color': 'rojo',
...
'numero': 2
...
'tipo': 'triangulo',
File "<stdin>", line 3
'numero': 2
^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
>>> try:
...
1 / 0
... random = 3
File "<stdin>", line 3
random = 3
^^^^^^
SyntaxError: expected 'except' or 'finally' block
Este tipo de excepción no es tan descriptivo como otros, pero, a veces, como
marca el punto exacto donde se ha encontrado el error, se puede adivinar
fácilmente cuál es la causa. La gran ventaja de que exista esta excepción es
que el error se da en tiempo de compilación y no en tiempo de ejecución,
por lo que se puede arreglar antes de lanzar la aplicación.
TypeError
Esta excepción se eleva cuando se intenta aplicar una operación o una función
sobre un objeto inapropiado. Algunos ejemplos son la suma o resta de números
con cadenas de caracteres o listas, la aplicación de funciones numéricas como
abs sobre objetos no numéricos u operaciones pensadas para operar sobre
secuencias aplicadas a elementos únicos. Veamos los siguientes ejemplos:
>>> abs('hola')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
>>> max(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
265
Python 2ªED.indb 265
10/1/22 13:40
Python a fondo
...
...
...
self.nombre = nombre
self.nombres.append(nombre)
>>> garfield = Gato('Garfield')
>>>
>>> bigotes = Gato('Bigotes')
>>> garfield.num_patas, bigotes.num_patas
(4, 4)
>>> bigotes.orejas = 1
>>> bigotes.orejas, garfield.orejas
(1, 2)
Como se puede ver en el ejemplo, todos los gatos tienen un número de patas
(num_patas) y un número de orejas (orejas) con los valores por defecto
4 y 2, especificado en la clase Gato, pero cuando se instancian los objetos
garfield y bigotes, se pueden modificar los valores de cada instancia.
Al añadir atributos de clase, no solo se establecen los valores por defecto
para cada instancia, sino que se puede acceder a esos atributos por medio
de la clase sin instanciar ningún objeto:
>>> vars(Gato)
mappingproxy({'__module__': '__main__', 'num_patas': 4,
'orejas': 2, 'nombres': ['Garfield', 'Bigotes'], '__init__':
<function Gato.__init__ at 0x10b16edc0>, '__dict__':
<attribute '__dict__' of 'Gato' objects>, '__weakref__':
<attribute '__weakref__' of 'Gato' objects>, '__doc__': None})
>>> Gato.num_patas
4
>>> Gato.orejas
2
>>> Gato.nombre
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Gato' has no attribute
'nombre'. Did you mean: 'nombres'?
Como se muestra en el ejemplo, como los atributos num_patas y orejas están definidos a nivel de clase, se pueden consultar esos valores sin
tener que crear una instancia de Gato. Sin embargo, si se intenta acceder al
278
Python 2ªED.indb 278
10/1/22 13:40
Capítulo 4 · Programación orientada a objetos
>>> print(dir(f))
['_Foo__atributo_cls_privado', '_Foo__x', '__class__',
'__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', '_atributo_
cls_protegido', '_x', 'obtener_x', 'obtener_x_privada',
'obtener_x_protegida', 'x']
>>> print(f.x)
2
>>> print(f._x)
4
>>> print(f.__x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__x'. Did you
mean: '_x'?
>>> print(Foo.__atributo_cls_privado)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '__atributo_
cls_privado'. Did you mean: '_Foo__atributo_cls_privado'?
>>> print(f.__x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__x'. Did you
mean: '_x'?
>>> print(f._Foo__x)
6
>>> print(Foo._atributo_cls_protegido)
0
>>> print(Foo.__atributo_cls_privado)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '__atributo_
cls_privado'. Did you mean: '_Foo__atributo_cls_privado'?
>>> print(Foo._Foo__atributo_cls_privado)
0
281
Python 2ªED.indb 281
10/1/22 13:40
Capítulo 4 · Programación orientada a objetos
8 C
ONTROLAR EL ESPACIO DE ATRIBUTOS
CON slots
Por defecto, en Python todos los atributos de una clase y de las instancias
se guardan dentro de las variables __dict__ o __weakref__, pero existe un método con el que se pueden controlar los atributos que deben ser
guardados (o no). Asimismo, ese mismo método define qué atributos son
accesibles desde el exterior. El método consiste en usar __slots__.
La definición de __slots__ se hace a nivel de clase y puede contener una
cadena de caracteres, un iterable o una secuencia de cadenas de caracteres con los nombres de los atributos usados por las instancias. Al definir
__slots__ se reserva espacio para las variables y se previene la creación
automática de __dict__ o __weakref__, además de bloquear la creación de nuevos atributos dinámicamente.
A continuación, se puede ver un ejemplo de cómo se definen y los usos
habituales:
>>> class Punto3D:
...
__slots__ = ['x', 'y', 'z']
...
def __init__(self, x, y, z):
...
...
...
...
...
self.x = x
self.y = y
self.z = z
>>> p = Punto3D(1, 2, 3)
>>> p.x, p.y, p.z
(1, 2, 3)
>>> p.nuevo_atributo = 89
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Punto3D' object has no attribute
'nuevo_atributo'
>>> p.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Punto3D' object has no attribute '__
dict__'. Did you mean: '__dir__'?
313
Python 2ªED.indb 313
10/1/22 13:41
Python a fondo
Sin embargo, gracias al uso de dataclasses se podría hacer una clase similar así:
@dataclass(order=True)
class MiNumeroDC:
valor: float = 0
Como se puede ver la cantidad de código es dramáticamente menor, pero
este es solo uno de los ejemplos más simples del uso de esta librería. A continuación, se muestra cómo utilizar el decorador y todas sus propiedades:
• @dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_
only=False, slots=False): es el decorador que se utiliza por defecto para
decorar cualquier clase y la definición de los parámetros es la siguiente:
− *: define que todos los argumentos hay que pasarlos usando clave valor.
− init: define si el método __init__() será generado o no.
− repr: define si el método __repr__() será generado o no. El método
por defecto genera una cadena del tipo f’<nombre_clase>(<attr1>=<valor_attr1>, <attr2=<valor_attr2>…)’. Si se
define manualmente el método __repr__() el parámetro se ignora.
− eq: define si el método __eq__(). Este método solo acepta comparaciones entre dos instancias idénticas.
− order: define los métodos __lt__(), __le__(), __gt__()
y __ge__() que comparan solo instancias idénticas y comparando atributo por atributo en ambas instancias.
− unsafe_hash: define si debería de intentar añadir un método __
hash__() automáticamente o no. El uso de este parámetro a True
puede desembocar fácilmente en errores y se recomienda consultar
la documentación oficial para hacer uso de este parámetro.
− frozen: si este parámetro se usa como True impedirá que se
modifique cualquier atributo o que se añadan nuevos.
− match_args: si este parámetro se usa como True (por defecto),
crea la tupla __match_args__ usando la lista de parámetros
usados en el método __init__() (desde 3.10).
− kw_only: si este parámetro se usa como True, se especifica que
todos los parámetros deben de pasarse como clave-valor para inicializar la clase (desde 3.10).
− slots: si este parámetro se usa como True, se definirá una nueva
clase que contiene __slots__ (desde 3.10).
ea
el
os
la
va
A continuación, se introducen algunos de los demás métodos incluidos en
el módulo dataclases que son útiles para la definición de clases:
320
Python 2ªED.indb 320
10/1/22 13:41
Capítulo 4 · Programación orientada a objetos
• dataclasses.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None, match_args=True, kw_only=False, slots=False):
este método define como debería de comportarse un atributo de la
clase y los parámetros se usan como sigue:
− *: define que todos los argumentos hay que pasarlos usando clave valor.
− default: define el valor por defecto de este atributo.
− default_factory: cuando un atributo debe de ser inicializado con
objetos mutables como listas o diccionarios es necesario utilizar este
parámetro para que se creen objetos diferentes en cada instancia.
− repr: define si este atributo deberá de aparecer o no en el método
por defecto __repr__.
− hash: define si este atributo deberá de aparecer o no en el método
por defecto __hash__.
− init: define si este atributo deberá de aparecer o no en el método
por defecto __init__.
− compare: define si este atributo debería de estar presente en los
métodos de comparación.
− metadata: si este parámetro puede ser un diccionario o None y
será envuelto en un objeto tipo MappingProxyType.
− match_args: si este parámetro se usa como True (por defecto),
crea la tupla __match_args__ usando la lista de parámetros
usados en el método __init__() (desde 3.10).
− kw_only: si este parámetro se usa como True, se especifica que
todos los parámetros deben de pasarse como clave-valor para inicializar la clase (desde 3.10).
− slots: si este parámetro se usa como True, se definirá una nueva
clase usando que contiene __slots__ (desde 3.10).
• dataclasses.fields(clase_o_instancia): devuelve una tupla con
los campos definidos en la clase o instancia.
• dataclasses.asdict(instancia, *, dict_factory=dict): convierte la
instancia a un diccionario con los atributos como clave y sus valores
convertidos utilizando la función dict_factory de forma recursiva.
• dataclasses.astuple(instancia, *, tuple_factory=dict): convierte
la instancia a una tupla de pares con los atributos y sus valores convertidos utilizando la función tuple_factory de forma recursiva.
321
Python 2ªED.indb 321
10/1/22 13:41
Python a fondo
• statistics: permite utilizar funciones estadísticas tales como medias,
medianas, modas o cuartiles en objetos numéricos como enteros,
fracciones o puntos flotantes. Desde la versión 3.10, también están
disponibles las funciones de cálculo de covarianza, correlación de
Pearson y la regresión lineal.
Módulos de programación funcional
• itertools: permite utilizar funciones iterativas eficientes. El uso de
este módulo es recomendable para generar secuencias de iteradores,
como combinaciones de iteradores, agrupaciones de elementos o
series repetitivas, entre otras.
• functools: permite crear funciones de orden superior en las que se
usan o se devuelven funciones. Se pueden utilizar para crear y cachear
métodos y para crear funciones o incluso métodos de clases parciales.
• operator: permite utilizar los operadores intrínsecos de Python
como funciones sin necesidad de instanciar las variables (por
ejemplo __lt__, __le__, __mod__, etc). Se suele utilizar con otras
funciones como map, reduce o algunas de las del módulo functools para declarar las funciones que realizar.
Módulos de acceso a ficheros y directorios
• pathlib: permite trabajar con las direcciones del sistema de ficheros
utilizando objetos. Contiene gran variedad de funciones para determinar las direcciones, detectar el tipo de los ficheros y crear direcciones
específicas para Windows o POSIX, entre otros usos. Algunas funcionalidades se solapan con las que se encuentran en el módulo os.
• os.path: permite operar con el sistema de ficheros a un nivel más
bajo que pathlib y añade algunas funcionalidades extra, como saber
la última modificación o tiempo de creación de ficheros, entre otras.
• fileinput: permite leer de varios flujos de entrada a la vez. Estos
flujos (streams) pueden ser múltiples ficheros o salidas estándar del
sistema. Para leer ficheros simples es mejor usar open().
• stat: contiene constantes y funciones para interpretar la salida de
los comandos stat, fstat, lstat y fstatat. Estos comandos
permiten conocer el estado de ficheros, y con este módulo se puede
conocer la información desde Python con facilidad.
• filecmp: permite comparar ficheros y directorios que parezcan similares. Se pueden definir parámetros como tiempos de creación o
similitudes. Para comparar el contenido de dos ficheros se recomienda usar difflib.
364
Python 2ªED.indb 364
10/1/22 13:41
Descargar