Búsqueda de sitios web

Funciones integradas de Python: una exploración completa


Python tiene muchas funciones integradas que puedes usar directamente sin importar nada. Estas funciones cubren una amplia variedad de tareas de programación comunes que incluyen realizar operaciones matemáticas, trabajar con tipos de datos integrados, procesar iterables de datos, manejar entradas y salidas en sus programas, trabajar con ámbitos y más.

En este tutorial podrás:

  • Conozca las funciones integradas de Python
  • Conozca los casos de uso comunes de las funciones integradas de Python
  • Utilice estas funciones para resolver problemas prácticos

Para aprovechar al máximo este tutorial, deberá estar familiarizado con la programación de Python, incluidos temas como trabajar con tipos de datos integrados, funciones, clases, decoradores, ámbitos y el sistema de importación.

Funciones integradas en Python

Python tiene varias funciones disponibles para que las use directamente desde cualquier parte de su código. Estas funciones se conocen como funciones integradas y cubren muchos problemas de programación comunes, desde cálculos matemáticos hasta características específicas de Python.

En este tutorial, aprenderá los conceptos básicos de las funciones integradas de Python. Al final, sabrá cuáles son sus casos de uso y cómo funcionan. Para comenzar, comenzará con las funciones integradas relacionadas con los cálculos matemáticos.

Uso de funciones integradas relacionadas con las matemáticas

En Python, encontrará algunas funciones integradas que se encargan de operaciones matemáticas comunes, como calcular el valor absoluto de un número, calcular potencias y más. Aquí hay un resumen de las funciones integradas relacionadas con las matemáticas en Python:

abs()

Calcula el valor absoluto de un número.

divmod()

Calcula el cociente y el resto de una división entera.

max()

Encuentra el mayor de los argumentos o elementos dados en un iterable

min()

Encuentra el más pequeño de los argumentos o elementos dados en un iterable

pow()

Eleva un número a una potencia

round()

Redondea un valor de punto flotante

sum()

Suma los valores en un iterable.

En las siguientes secciones, aprenderá cómo funcionan estas funciones y cómo usarlas en su código Python.

Obtener el valor absoluto de un número: abs()

El valor absoluto o módulo de un número real es su valor no negativo. En otras palabras, el valor absoluto es el número sin su signo. Por ejemplo, el valor absoluto de -5 es 5 y el valor absoluto de 5 también es 5.

La función abs() incorporada de Python le permite calcular rápidamente el valor absoluto o módulo de un número:

>>> from decimal import Decimal
>>> from fractions import Fraction

>>> abs(-42)
42
>>> abs(42)
42

>>> abs(-42.42)
42.42
>>> abs(42.42)
42.42

>>> abs(complex("-2+3j"))
3.605551275463989
>>> abs(complex("2+3j"))
3.605551275463989

>>> abs(Fraction("-1/2"))
Fraction(1, 2)
>>> abs(Fraction("1/2"))
Fraction(1, 2)

>>> abs(Decimal("-0.5"))
Decimal('0.5')
>>> abs(Decimal("0.5"))
Decimal('0.5')

En estos ejemplos, calcula el valor absoluto de diferentes tipos numéricos utilizando la función abs(). Primero, se utilizan números enteros, luego números de punto flotante y complejos y, finalmente, números fraccionarios y decimales. En todos los casos, cuando llamas a la función con un valor negativo, el resultado final elimina el signo.

Como ejemplo práctico, supongamos que necesita calcular las ganancias y pérdidas totales de su empresa a partir de las transacciones de un mes:

>>> transactions = [-200, 300, -100, 500]

>>> incomes = sum(income for income in transactions if income > 0)
>>> expenses = abs(
...     sum(expense for expense in transactions if expense < 0)
... )

>>> print(f"Total incomes: ${incomes}")
Total incomes: $800
>>> print(f"Total expenses: ${expenses}")
Total expenses: $300
>>> print(f"Total profit: ${incomes - expenses}")
Total profit: $500

En este ejemplo, para calcular los gastos, utiliza la función abs() para obtener el valor absoluto de los gastos, lo que da como resultado un valor positivo.

Encontrar el cociente y el resto en la división: divmod()

Python proporciona una función incorporada llamada divmod() que toma dos números como argumentos y devuelve una tupla con el cociente y el resto que resultan de la división entera de los números de entrada:

>>> divmod(8, 4)
(2, 0)

>>> divmod(6.5, 3.5)
(1.0, 3.0)

Con números enteros como argumentos, el resultado es el mismo que (a/b, a % b). Con números de punto flotante, el resultado es (q, a % b), donde q suele ser math.floor(a/b) , pero puede ser 1 menor que eso.

Como ejemplo práctico de cuándo usar esta función, digamos que desea codificar una función que toma un valor de tiempo en milisegundos y devuelve una cadena con el formato "00:00:00". Aquí hay una posible implementación usando la función divmod():

>>> def hh_mm_ss(milliseconds):
...     seconds = round(milliseconds / 1000)
...     minutes, seconds = divmod(seconds, 60)
...     hours, minutes = divmod(minutes, 60)
...     return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
...

>>> hh_mm_ss(10000)
'00:00:10'
>>> hh_mm_ss(68000)
'00:01:08'
>>> hh_mm_ss(3680000)
'01:01:20'

En esta función, primero convierte los milisegundos de entrada a segundos y redondea el resultado al número entero más cercano.

Luego, usas divmod() para dividir el total de segundos entre 60 porque hay 60 segundos en un minuto. Este cálculo le proporciona los minutos y los segundos restantes. Finalmente, usas divmod() nuevamente para dividir los minutos entre 60 porque hay 60 minutos en una hora. Esta vez, obtienes las horas y los minutos restantes.

Encontrar valores mínimos y máximos: min() y max()

A veces, es necesario encontrar los valores menor y mayor en un iterable o en una serie de valores. Estos pueden ser cálculos comunes en programación y Python tiene funciones integradas para ellos.

La función min() le permite encontrar el valor mínimo en un iterable, mientras que la función max() le ayuda a encontrar el valor máximo. Aquí está la firma de ambas funciones:

min(iterable, *[, default, key])
max(iterable, *[, default, key])

Ambas funciones toman un argumento requerido llamado iterable y devuelven los valores mínimo y máximo, respectivamente. También toman dos argumentos opcionales de solo palabras clave:

default

Puede contener el valor que desea devolver si la entrada iterable está vacía

key

Acepta una función de un solo argumento para personalizar los criterios de comparación.

A continuación se muestran algunos ejemplos rápidos de cómo utilizar las funciones min() y max() con diferentes conjuntos de argumentos:

>>> min([1, 2, 3, 4])
1
>>> max([1, 2, 3, 4])
4

>>> min(1, 2, 3, 4)
1
>>> max(1, 2, 3, 4)
4

>>> min([], default=0)
0
>>> max([], default=0)
0

>>> min([-2, 3, 4, -5, 1], key=abs)
1
>>> max([-2, 3, 4, -5, 1], key=abs)
-5

En los dos primeros ejemplos, utiliza min() y max() con una lista de números. También puedes utilizar estas funciones con una serie de argumentos posicionales.

Luego, tiene dos ejemplos del uso del argumento default para devolver un valor adecuado cuando la entrada iterable está vacía. Finalmente, tienes dos ejemplos del uso del argumento key. En estos ejemplos, utiliza la función abs() para proporcionar los criterios de comparación.

Poderes informáticos: pow()

Cuando necesite calcular potencias en su código, puede utilizar la función incorporada pow(). Esta función toma un número y lo eleva a una potencia determinada. Aquí está la firma de la función:

pow(base, exp[, mod=None])

Cuando llamas a pow(), obtienes base elevado a la potencia de exp. Con estos dos argumentos, pow() es equivalente a algo como base**exp:

>>> pow(2, 8)
256

>>> 2**8
256

Esta operación calcula 2 elevado a 8, que es 256. Esto es equivalente a una operación poderosa con el operador **, que encontrará con mayor frecuencia en el código del mundo real.

El argumento mod le permite hacer algo como pow(base, exp) % mod pero calculado de manera más eficiente:

>>> import timeit

>>> base = 2
>>> exp = 1000000
>>> mod = 1000000

>>> timeit.timeit(
...     "pow(base, exp, mod)", globals=globals(), number=10
... ) * 1000
0.021042011212557554

>>> timeit.timeit(
...     "pow(base, exp) % mod", globals=globals(), number=10
... ) * 1000
61.956208024639636

En este ejemplo, utiliza la función timeit() del módulo timeit para medir la velocidad de cálculo. Luego, define algunas variables para realizar el cálculo. En la primera llamada a timeit(), utiliza el argumento mod. En la segunda llamada, utiliza el operador de módulo (%).

Cuando comparas el consumo de tiempo resultante, puedes concluir que usar el argumento mod es mucho más rápido que calcular la potencia y luego aplicar el operador de módulo como en pow(base, exp) % mod.

Redondeo de números: round()

La función round() incorporada de Python toma un argumento numérico y lo devuelve redondeado a un número determinado de dígitos.

La firma de round() se muestra en el siguiente código:

round(number[, ndigits])

En esta firma, number suele ser un número de punto flotante, mientras que ndigits es un argumento opcional que debe ser un número entero. Este último argumento definirá la precisión o el número de dígitos después del punto decimal.

A continuación se muestran algunos ejemplos:

>>> from math import pi
>>> pi
3.141592653589793

>>> round(pi, 2)
3.14

>>> round(pi, 4)
3.1416

>>> round(pi, 6)
3.141593

En estos ejemplos, utiliza la constante pi del módulo matemático y la función round() para expresar el número con diferente precisión.

Cuando usas round() con un solo argumento, puedes obtener resultados sorprendentes:

>>> round(1.5)
2

>>> round(2.5)
2

En estos dos ejemplos, la función round() redondea 1,5 hasta 2 y 2,5 hacia abajo hasta 2. Esto se debe a que round() redondea al múltiplo más cercano de 10 a la potencia menos ndigits. Si dos múltiplos son igualmente cercanos, el redondeo se realiza hacia la opción par. Esta estrategia de redondear de mitad a par ayuda a mitigar el sesgo de redondeo. Es por eso que 2.5 se redondea a 2 en lugar de 3.

Calcular totales: sum()

La función sum() incorporada de Python proporciona una forma pitónica eficiente de sumar una lista de valores numéricos, que también es un paso intermedio común en muchos cálculos. Entonces sum() es una herramienta bastante útil para un programador de Python.

La función sum() le permite sumar una serie de valores. Su firma es la siguiente:

sum(iterable[, start=0])

Puede llamar a sum() con los dos argumentos siguientes:

iterable

Un argumento obligatorio que puede contener cualquier iterable de Python.

start

Un argumento opcional que puede contener un valor inicial.

Cuando llamas a sum(), la función agrega internamente start más los valores en iterable. Los elementos de la entrada iterable suelen ser valores numéricos. Sin embargo, también puedes utilizar listas o tuplas. El argumento start puede aceptar un número, una lista o una tupla, dependiendo de lo que contenga su iterable.

Aquí hay algunos ejemplos de cómo usar sum() con diferentes entradas:

>>> sum([])
0

>>> sum([1, 2, 3, 4, 5])
15

>>> sum([1, 2, 3, 4, 5], 100)  # As a positional argument
115

>>> sum([1, 2, 3, 4, 5], start=100)  # As a keyword argument
115

>>> num_lists = [[1, 2, 3], [4, 5, 6]]
>>> sum(num_lists, start=[])
[1, 2, 3, 4, 5, 6]

>>> num_tuples = ((1, 2, 3), (4, 5, 6))
>>> sum(num_tuples, start=())
(1, 2, 3, 4, 5, 6)

Cuando llamas a sum() con un iterable vacío, obtienes 0 como resultado porque ese es el valor predeterminado de start. Llamar a la función con una lista de valores devuelve la suma total de los valores proporcionados.

Si desea utilizar un valor start distinto de 0, puede proporcionarlo como argumento posicional o de palabra clave. Sin embargo, este último enfoque es más legible.

Los dos últimos ejemplos muestran que también puedes usar sum() para concatenar listas y tuplas. Tenga en cuenta que para que este truco funcione, debe configurar start en el objeto apropiado. Si desea concatenar listas, entonces start debe contener una lista, y así sucesivamente. Aunque este truco funciona, la práctica no es eficiente ni común. En su lugar, deberías utilizar el operador más (+) para concatenaciones regulares.

Un ejemplo clásico de uso de sum() es cuando necesitas calcular la media o el promedio de varios valores numéricos. En esta situación, es necesario sumar los datos de entrada como paso intermedio. He aquí un ejemplo:

def mean(values):
    try:
        return sum(values) / len(values)
    except ZeroDivisionError:
        raise ValueError("mean() arg shouldn't be empty") from None

En esta función mean(), utiliza sum() para sumar los valores de entrada y luego divide el resultado por el número de valores en los datos de entrada.

Creación y manipulación de tipos de datos básicos

Python tiene varias funciones integradas que le permiten manipular tipos de datos básicos, como números enteros y de punto flotante, cadenas y valores booleanos. A continuación se muestra un resumen de las funciones integradas que le ayudan a procesar tipos de datos básicos:

int()

Construye un objeto entero a partir de un número o cadena

bin()

Convierte un número entero en una cadena binaria

oct()

Convierte un número entero en una cadena octal

hex()

Convierte un número entero en una cadena hexadecimal

float()

Construye un objeto de punto flotante a partir de un número o cadena

complex()

Construye un número complejo a partir de argumentos.

str()

Crea un objeto de cadena

repr()

Crea una representación de cadena de un objeto fácil de usar para desarrolladores.

bool()

Convierte un argumento en un valor booleano

ord()

Busca el punto de código entero de un carácter.

chr()

Busca el carácter del punto de código entero dado

bytes()

Crea un objeto bytes (similar a bytearray, pero inmutable)

bytearray()

Crea un objeto de la clase bytearray

En las siguientes secciones, aprenderá los conceptos básicos para trabajar con estas funciones y cómo usarlas en su código Python.

Representación de números enteros: int(), bin(), oct() y hex()

Los números enteros son bastante útiles en programación. Python tiene un tipo de datos incorporado llamado int que representa números enteros. Cuando se trabaja con números enteros, a veces es necesario expresarlos en diferentes bases como 2, 8 o 16. Es posible que también necesite convertir cadenas u otros tipos numéricos a números enteros.

Para la última tarea, puede utilizar la función incorporada int(). A continuación se muestran algunos ejemplos de su uso:

>>> int()
0

>>> int(42.42)
42

>>> int("42")
42

>>> int("42.42")
Traceback (most recent call last):
    ...
ValueError: invalid literal for int() with base 10: '42.42'

>>> int("one")
Traceback (most recent call last):
    ...
ValueError: invalid literal for int() with base 10: 'one'

Sin argumento, int() devuelve 0. Este comportamiento es especialmente útil cuando necesita una función de fábrica para clases como defaultdict del módulo collections. Con números de punto flotante, int() simplemente elimina la parte decimal y devuelve la parte completa. Finalmente, con una cadena como argumento, int() devuelve el número entero correspondiente sólo si la cadena representa un número entero válido.

También puedes usar int() para convertir una representación de cadena binaria, octal o hexadecimal en un número entero:

>>> int("0b10", base=2)
2

>>> int("0o10", base=8)
8

>>> int("0x10", base=16)
16

En el primer ejemplo, utiliza int() para convertir una cadena que representa un número en formato binario a su entero decimal equivalente. Tenga en cuenta que para que esta operación funcione, debe establecer el argumento base en la base adecuada, que es 2 para números binarios. A continuación, realiza conversiones similares con cadenas octales y hexadecimales. Nuevamente, debe establecer base en el valor apropiado.

Las funciones bin(), oct() y hex() le permiten realizar la operación opuesta. Con ellos, puedes convertir un número entero dado en su representación binaria, octal o hexadecimal:

>>> bin(42)
'0b101010'

>>> oct(42)
'0o52'

>>> hex(42)
'0x2a'

En estos ejemplos, utiliza un número entero como argumento para bin(), oct() y hex(). Como resultado, obtiene la representación de cadena del valor de entrada en formato binario, octal y hexadecimal, respectivamente.

Manipulación de otros números: float() y complex()

Python tiene tipos básicos integrados para representar números de punto flotante y complejos. Estos tipos tienen funciones integradas asociadas para fines de conversión. Entonces, para números de punto flotante, tienes la función float(), y para números complejos tienes complex().

Aquí están las firmas de ambas funciones:

float(number=0.0)

complex(real=0, imag=0)
complex(string)

La función float() toma un único argumento que representa un valor numérico. Este argumento acepta números o cadenas que representan números válidos:

>>> float()
0.0

>>> float(42)
42.0

>>> float("42")
42.0

>>> float("3.14")
3.14

>>> float("one")
Traceback (most recent call last):
    ...
ValueError: could not convert string to float: 'one'

Sin argumentos, float() devuelve 0.0. Con números enteros, devuelve el número de punto flotante equivalente con 0 como parte decimal. Con cadenas que representan números, float() devuelve el número de punto flotante equivalente. Sin embargo, falla si la cadena de entrada no representa un valor numérico válido.

La función complex() le permite trabajar con números complejos. Esta función tiene dos firmas diferentes. La primera firma tiene dos argumentos:

real

La parte real del número.

imag

La parte imaginaria del número.

Estos argumentos aceptan valores numéricos, como números enteros o de punto flotante. Así es como funciona esta variación de complex():

>>> complex(3, 6)
(3+6j)

>>> complex(1, 0)
(1+0j)

>>> complex(0, 1)
1j

>>> complex(3.14, -2.75)
(3.14-2.75j)

Puede llamar a complex() con valores numéricos, lo que da como resultado un número complejo. Tenga en cuenta que Python usa un j para definir la parte imaginaria.

La segunda firma de complex() toma un único argumento que debería ser una cadena:

>>> complex("3+6j")
(3+6j)

>>> complex("1+0j")
(1+0j)

>>> complex("1j")
1j

>>> complex("3.14-2.75j")
(3.14-2.75j)

Cuando usa cadenas para crear números complejos con complex(), debe asegurarse de que la cadena de entrada tenga un formato válido. Debe estar formado por la parte real, el signo y la parte imaginaria. No puede agregar espacios para separar estos componentes.

Construyendo y representando cadenas: str() y repr()

Cuando se trata de crear y trabajar con cadenas de Python, hay que considerar dos funciones integradas fundamentales:

    str()
    repr()

Con la función str(), puede crear nuevas cadenas o convertir objetos existentes en cadenas:

>>> str()
''

>>> str(42)
'42'

>>> str(3.14)
'3.14'

>>> str([1, 2, 3])
'[1, 2, 3]'

>>> str({"one": 1, "two": 2, "three": 3})
"{'one': 1, 'two': 2, 'three': 3}"

>>> str({"A", "B", "C"})
"{'B', 'C', 'A'}"

En el primer ejemplo, usas str() sin un argumento para crear una cadena vacía. En los otros ejemplos, obtienes cadenas con representaciones fáciles de usar de los objetos de entrada.

Para un caso de uso práctico, digamos que tiene una lista de valores numéricos y desea unirlos usando el método str.join(), que solo acepta iterables de cadenas. En este caso, puedes hacer algo como lo siguiente:

>>> "-".join(str(value) for value in [1, 2, 3, 4, 5])
'1-2-3-4-5'

En este ejemplo, utiliza una expresión generadora para convertir cada número a su representación de cadena antes de llamar a .join(). De esta manera, evita recibir un error en su código.

Por su parte, la función incorporada repr() le brinda una representación fácil de usar para el desarrollador del objeto en cuestión:

>>> repr(42)
'42'

>>> repr(3.14)
'3.14'

>>> repr([1, 2, 3])
'[1, 2, 3]'

>>> repr({"one": 1, "two": 2, "three": 3})
"{'one': 1, 'two': 2, 'three': 3}"

>>> repr({"A", "B", "C"})
"{'B', 'C', 'A'}"

Para los tipos integrados, la representación de cadena que se obtiene con repr() es la misma que se obtiene con la función str().

Para ver la diferencia entre str() y repr(), considere el siguiente ejemplo que utiliza el módulo datetime:

>>> import datetime
>>> today = datetime.datetime.now()

>>> repr(today)
'datetime.datetime(2024, 7, 1, 12, 38, 53, 180208)'

>>> str(today)
'2024-07-01 12:38:53.180208'

El método repr() le proporciona una representación de cadena fácil de usar para desarrolladores del objeto datetime. Idealmente, debería poder recrear el objeto utilizando esta representación. En otras palabras, debería poder copiar y pegar la representación resultante para recrear el objeto. Es por eso que se dice que esta representación de cadena es amigable para los desarrolladores.

Por el contrario, la representación de cadena que se obtiene al llamar a str() debe ser legible e informativa para los usuarios finales.

Procesamiento de valores booleanos: bool()

La función bool() incorporada de Python le permite determinar el valor de verdad de cualquier objeto de Python. Es una función predicada porque siempre devuelve True o False. Para determinar si un objeto es falso, en otras palabras, si bool() devuelve False cuando se aplica al objeto, Python usa lo siguiente reglas internas:

  • Constantes definidas como falsas: Ninguna y False
  • El cero de cualquier tipo numérico: 0, 0.0, 0j, Decimal(0), Fracción (0, 1)
  • Secuencias y colecciones vacías: '', (), [], {}, set( ), rango(0)

El resto de los objetos se consideran veraces en Python. Los objetos personalizados se consideran veraces de forma predeterminada a menos que proporcionen un método especial .__bool__() que defina un comportamiento diferente.

A continuación se muestran algunos ejemplos de cómo funciona bool():

>>> bool()
False

>>> bool(0)
False
>>> bool(42)
True

>>> bool(0.0)
False
>>> bool(3.14)
True

>>> bool("")
False
>>> bool("Hello")
True

>>> bool([])
False
>>> bool([1, 2, 3])
True

En el primer ejemplo, llamas a bool() sin un argumento y obtienes False como resultado. En el resto de los ejemplos, puede confirmar que Python aplica consistentemente las reglas enumeradas anteriormente. En la práctica, sólo necesitarás usar bool() cuando tu código requiera explícitamente un valor booleano en lugar de un objeto diferente.

Como ejemplo del uso de bool(), digamos que tiene la siguiente implementación de una estructura de datos de pila:

class Stack:
    def __init__(self, items=None):
        self.items = list(items) if items is not None else []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __bool__(self):
        return bool(self.items)

En este ejemplo, su clase Stack implementa el método especial .__bool__() para admitir operaciones booleanas en sus objetos. Este método garantiza que cuando un objeto Stack determinado está vacío, la función bool() devuelve False y True en caso contrario. . He aquí un ejemplo:

>>> from stack import Stack

>>> stack = Stack()

>>> bool(stack)
False

>>> stack.push(4)

>>> bool(stack)
True

En este fragmento de código, primero crea una pila vacía. Cuando pasas este objeto a bool(), obtienes False. Luego, inserta un valor en la pila y llama a bool() nuevamente. Esta vez obtienes True porque la pila ya no está vacía.

Cadenas de codificación: ord() y chr()

La codificación de caracteres es un tema importante en la mayoría de los lenguajes de programación. En Python, las cadenas utilizan los caracteres Unicode establecidos de forma predeterminada. Cada carácter Unicode tiene un punto de código asociado, que es un número entero. Para obtener el punto de código de un carácter determinado, puede utilizar la función incorporada ord():

>>> ord("A")
65

>>> ord("Z")
90

>>> ord("x")
120

>>> ord("ñ")
241

>>> ord("&")
38

Cada carácter Unicode tiene un punto de código asociado que identifica de forma única el carácter en la tabla Unicode. En estos ejemplos, utiliza la función para obtener el punto de código de algunos caracteres.

En la práctica, puede utilizar la función ord() para implementar técnicas criptográficas básicas, ordenar cadenas o caracteres, validar caracteres de entrada, etc. Aquí hay un ejemplo rápido de una función que solo verifica si todos los caracteres en una cadena son letras mayúsculas del alfabeto inglés:

>>> def is_uppercase(text):
...     for char in text:
...         if not (65 <= ord(char) <= 90):
...             return False
...     return True
...

>>> is_uppercase("HELLO")
True

>>> is_uppercase("Hello")
False

En esta función, utiliza ord() para determinar si los caracteres en una cadena están entre 65 y 90, que es el intervalo de código puntos para letras mayúsculas, de la A a la Z, en la tabla Unicode.

A veces, es posible que necesites determinar el punto de código que identifica un carácter Unicode determinado. En esta situación, puede utilizar la función incorporada chr():

>>> chr(65)
'A'

>>> chr(90)
'Z'

>>> chr(120)
'x'

>>> chr(241)
'ñ'

>>> chr(38)
'&'

La función chr() realiza la operación opuesta a ord(). Le permite encontrar el punto de código asociado con un carácter específico.

Las funciones ord() y chr() son en cierto modo complementarias y, por lo tanto, probablemente las encontrará utilizadas juntas.

Creando Bytes y Bytearrays: bytes() y bytearray()

Los bytes y las matrices de bytes de Python son tipos integrados que Python proporciona de forma inmediata para manipular datos binarios, codificar y decodificar texto, procesar la entrada y salida de archivos y comunicarse a través de redes.

El tipo de datos bytes es inmutable, mientras que el tipo bytearray es mutable. Para crear objetos derivados de estos tipos de datos, puede utilizar las funciones integradas bytes() y bytearray().

Las funciones bytes() y bytearray() tienen las siguientes firmas:

bytes(source=b"")
bytes(source, encoding)
bytes(source, encoding, errors)

bytearray(source=b"")
bytearray(source, encoding)
bytearray(source, encoding, errors)

Ambas funciones tienen tres firmas diferentes. La primera firma de ambas funciones acepta un literal bytes como argumento. Estos literales son similares a los literales de cadena, pero comienzan con b y solo aceptan caracteres ASCII.

Aquí hay un resumen de los argumentos y su significado:

source

Un literal bytes o una cadena

encoding

La codificación de caracteres que se utilizará para decodificar source si contiene una cadena

errors

Un controlador para errores de codificación y decodificación.

El argumento encoding solo es necesario si el argumento source es una cadena, en cuyo caso, debe proporcionar la codificación adecuada para que Python pueda convertir la cadena en bytes.

Finalmente, los argumentos errors también son opcionales y deben contener uno de los siguientes controladores de errores:

"strict"

Genera una excepción UnicodeDecodeError o UnicodeEncodeError cuando aparecen problemas de codificación.

"ignore"

Ignora los caracteres que no se pueden codificar.

"replace"

Reemplaza los caracteres que no se pueden codificar con un signo de interrogación (?)

"xmlcharrefreplace"

Reemplaza los caracteres que no se pueden codificar con una referencia de caracteres XML

"backslashreplace"

Reemplaza los caracteres que no se pueden codificar con las secuencias de escape de barra invertida de Python

Al elegir los controladores de errores adecuados, puede configurar una buena estrategia para aquellas situaciones en las que llama a las funciones bytes() y bytearray() con datos erróneos.

A continuación se muestran algunos ejemplos del uso de las funciones bytes() y bytearray():

>>> bytes()
b''

>>> bytes(b"Using ASCII characters or bytes \xc3\xb1")
b'Using ASCII characters or bytes \xc3\xb1'

>>> bytes("Using non-ASCII characters: ñ Ł", encoding="utf-8")
b'Using non-ASCII characters: \xc3\xb1 \xc5\x81'

>>> bytearray()
bytearray(b'')

>>> bytearray(b"Using ASCII characters or bytes \xc3\xb1")
bytearray(b'Using ASCII characters or bytes \xc3\xb1')

>>> bytearray("Using non-ASCII characters: ñ Ł", encoding="utf-8")
bytearray(b'Using non-ASCII characters: \xc3\xb1 \xc5\x81')

En estos ejemplos, crea objetos bytes y bytearray utilizando literales bytes y cadenas con la codificación correcta como argumento. Tenga en cuenta que puede llamar a la función bytes() sin argumentos para crear un objeto bytes vacío.

Ahora considere los siguientes ejemplos que muestran cómo utilizar controladores de errores:

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii"
... )
Traceback (most recent call last):
    ...
UnicodeEncodeError: 'ascii' codec can't encode character '\xf1'
    in position 52: ordinal not in range(128)

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="ignore"
... )
b'Using non-ASCII characters with the ASCII encoding:  '

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="replace"
... )
b'Using non-ASCII characters with the ASCII encoding: ? ?'

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="xmlcharrefreplace"
... )
b'Using non-ASCII characters with the ASCII encoding: &#241; &#321;'

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="backslashreplace"
... )
b'Using non-ASCII characters with the ASCII encoding: \\xf1 \\u0141'

En estos ejemplos, solo usas bytes() porque bytearray() funcionaría de manera similar. La única diferencia es que bytes() devuelve objetos inmutables mientras que bytearray() devuelve objetos mutables.

Estos ejemplos utilizan caracteres que no son ASCII con codificación ASCII, lo que provocará errores de codificación que deberá solucionar. El valor predeterminado del argumento errors es "strict". Es por eso que obtienes una excepción UnicodeEncodeError en el primer ejemplo anterior.

Luego configura errors en "ignore" para que Python ignore cualquier error de codificación. En este caso, se eliminan los caracteres ñ y Ł. Si configura errors en "replace", entonces ñ y Ł se reemplazan cada uno con un signo de interrogación.

El uso de "xmlcharrefreplace" como controlador de errores hace que Python reemplace los caracteres ñ y Ł con sus respectivas referencias de caracteres XML. Finalmente, usar "backslashreplace" escapa de los caracteres problemáticos usando la secuencia de escape apropiada.

Crear tipos de datos de colección

Una característica fundamental de Python es el rico conjunto de tipos de datos de recopilación integrados en el lenguaje. Tendrá varias funciones integradas que le permitirán manipular estos tipos de datos, que incluyen listas, tuplas, diccionarios, conjuntos y bytes.

A continuación se muestra un resumen de las funciones integradas que le ayudan a procesar los tipos de datos de recopilación:

list()

Crea un objeto list a partir de un iterable

tuple()

Crea un objeto tuple a partir de un iterable

dict()

Crea un objeto dict a partir de una serie de pares clave-valor o argumentos de palabras clave

set()

Crea un objeto set a partir de un iterable

frozenset()

Crea un objeto frozenset a partir de un iterable

En las siguientes secciones, aprenderá los conceptos básicos para trabajar con estas funciones y usarlas para crear y manipular colecciones en su código Python.

Creando listas y tuplas: list() y tuple()

La list de Python es un tipo de datos integrado fundamental con un impresionante conjunto de características. Las listas son mutables y le permiten organizar y manipular de manera eficiente datos que pueden ser heterogéneos pero que normalmente son homogéneos. Por ejemplo, puede utilizar una lista para almacenar una columna de una tabla de base de datos.

De manera similar, la tupla de Python es otro tipo integrado. A diferencia de las listas, las tuplas son inmutables. Puede utilizarlos para organizar datos que pueden ser homogéneos pero que normalmente son heterogéneos. Por ejemplo, puedes utilizar una tupla para almacenar una fila de una tabla de base de datos.

Las funciones integradas list() y tuple() de Python te permiten crear objetos list y tuple.

La función list() toma un iterable como argumento y devuelve un objeto list creado a partir de los datos de entrada. Entonces, su firma se parece a la siguiente:

list([iterable])

Tenga en cuenta que los corchetes alrededor de iterable significan que el argumento es opcional, por lo que los corchetes no forman parte de la sintaxis.

A continuación se muestran algunos ejemplos del uso de list() para crear objetos list:

>>> list()
[]

>>> list("Hello")
['H', 'e', 'l', 'l', 'o']

>>> list((1, 2, 3, 4, 5))
[1, 2, 3, 4, 5]

>>> list({"circle", "square", "triangle", "rectangle", "pentagon"})
['square', 'rectangle', 'triangle', 'pentagon', 'circle']

>>> list({"name": "John", "age": 30, "city": "New York"})
['name', 'age', 'city']
>>> list({"name": "John", "age": 30, "city": "New York"}.keys())
['name', 'age', 'city']

>>> list({"name": "John", "age": 30, "city": "New York"}.values())
['John', 30, 'New York']

>>> list({"name": "John", "age": 30, "city": "New York"}.items())
[('name', 'John'), ('age', 30), ('city', 'New York')]

Cuando llamas a list() sin un argumento, creas una nueva lista vacía. Cuando usas una cadena como argumento, creas una lista de caracteres. Cuando usas una tupla, la conviertes en una lista.

La función list() incluso acepta conjuntos, pero debes recordar que los conjuntos son estructuras de datos desordenadas, por lo que no podrás predecir el orden final de los elementos en la lista resultante.

Cuando se trata de utilizar diccionarios con list(), tienes cuatro posibilidades. Puede crear una lista de claves usando el diccionario directamente o usando su método .keys(). Si desea crear una lista de valores, puede utilizar el método .values(). Finalmente, si desea crear una lista de pares clave-valor, puede utilizar el método .items().

Las listas tienen muchos casos de uso en el código Python. Son flexibles, potentes y llenos de funciones, por lo que los encontrará en casi todos los fragmentos de código Python.

Las tuplas se utilizan habitualmente para almacenar datos heterogéneos e inmutables. La función tuple() le permite crear tuplas sobre la marcha. Aquí está la firma:

tuple([iterable])

Los corchetes alrededor de iterable significan que el argumento es opcional, por lo que los corchetes no forman parte de la sintaxis.

Considere los siguientes ejemplos del uso de tuple() en su código:

>>> tuple()
()

>>> tuple("Hello")
('H', 'e', 'l', 'l', 'o')

>>> tuple(["Jane Doe", 25, 1.75, "Canada"])
('Jane Doe', 25, 1.75, 'Canada')

>>> tuple({
...     "manufacturer": "Ford",
...     "model": "Mustang",
...     "color": "Blue",
... }.values())
('Ford', 'Mustang', 'Blue')

Puedes usar tuple() sin argumentos para crear una tupla vacía. Esto será más legible que usar un par de paréntesis vacíos (). Cuando pasas una cadena a tuple(), obtienes una tupla de caracteres.

En el tercer ejemplo, utiliza tuple() para convertir una lista de datos heterogéneos en una tupla, que sería una estructura de datos más apropiada para almacenar este tipo de datos. Finalmente, usas los valores de un diccionario para construir una tupla.

Al igual que las listas, las tuplas son bastante útiles en Python. Los verá utilizados en muchos casos de uso, especialmente en aquellas situaciones en las que necesita almacenar datos heterogéneos e inmutables.

Construyendo diccionarios: dict()

Los diccionarios son una estructura de datos integrada fundamental en Python. Están en todas partes y son una parte central del propio idioma. Encontrará muchos casos de uso para diccionarios en su código. En cuanto a otras colecciones integradas, Python también tiene una función integrada que le permite crear diccionarios: la función dict().

La función dict() tiene las siguientes firmas:

dict(**kwargs)
dict(mapping, **kwargs)
dict(iterable, **kwargs)

Todas estas firmas aceptan lo que se conoce como argumentos de palabras clave (**kwargs) o argumentos con nombre. La segunda firma toma un mapeo, que puede ser otro diccionario. Finalmente, la tercera firma acepta un iterable de pares clave-valor, que puede ser una lista de tuplas de dos elementos, por ejemplo.

A continuación se muestran algunos ejemplos rápidos del uso de la función dict() de diferentes maneras:

>>> dict()
{}

>>> jane = dict(name="Jane", age="30", country="Canada")
>>> jane
{'name': 'Jane', 'age': '30', 'country': 'Canada'}

>>> dict(jane, job="Python Dev")
{'name': 'Jane', 'age': '30', 'country': 'Canada', 'job': 'Python Dev'}

>>> dict([("name", "Jane"), ("age", 30), ("country", "Canada")])
{'name': 'Jane', 'age': 30, 'country': 'Canada'}

Nuevamente, al crear un diccionario vacío, puede usar la función dict() sin argumentos. Esto es menos común que usar un par de llaves {}, pero nuevamente, puede ser más legible y explícito en algunos contextos.

Luego, crea un diccionario jane utilizando argumentos de palabras clave. Esta es una forma limpia y elegante de crear diccionarios en Python.

El tercer ejemplo muestra cómo se puede combinar una asignación con argumentos de palabras clave para crear un nuevo objeto de diccionario. Finalmente, en el cuarto ejemplo, crea un nuevo diccionario a partir de una lista de tuplas.

Creación de conjuntos y conjuntos congelados: set() y frozenset()

El set de Python es un tipo de datos integrado para crear colecciones de objetos únicos y hashable, normalmente llamados elementos o miembros. En Python, los conjuntos admiten las operaciones definidas para conjuntos matemáticos, incluidas unión, diferencia, diferencia simétrica y otras.

Python tiene dos tipos de conjuntos:

    set
    frozenset

La diferencia entre estos dos tipos de datos es que los objetos set son mutables y los objetos frozenset son inmutables.

Al igual que con otros tipos de datos, Python también proporciona funciones integradas para crear conjuntos y conjuntos congelados. Tendrás las funciones set() y frozenset(), respectivamente. La firma de estas funciones se muestra a continuación:

set([iterable])
frozenset([iterable])

Nuevamente, los corchetes indican que la entrada iterable es opcional. Ahora consulte los siguientes ejemplos de creación de conjuntos y conjuntos congelados:

>>> set()
set()

>>> frozenset()
frozenset()

>>> set(["square", "rectangle", "triangle", "pentagon", "circle"])
{'square', 'triangle', 'circle', 'rectangle', 'pentagon'}

>>> frozenset(["square", "rectangle", "triangle", "pentagon", "circle"])
frozenset({'square', 'triangle', 'circle', 'rectangle', 'pentagon'})

>>> set(("red", "green", "blue", "red"))
{'green', 'red', 'blue'}

>>> frozenset(("red", "green", "blue", "red"))
frozenset({'green', 'red', 'blue'})

Cuando llamas a set() y frozenset() sin argumentos, creas un conjunto vacío o un conjunto congelado, respectivamente. En el caso de conjuntos, no tiene un literal que pueda usar para crear un conjunto vacío porque un par de llaves ({}) definen un diccionario vacío. Entonces, para crear un conjunto vacío, debes usar la función set().

En el resto de los ejemplos, se utilizan iterables, como listas y tuplas, para crear conjuntos y conjuntos congelados. Es importante tener en cuenta que cuando el iterable de entrada tiene elementos repetidos, el conjunto final tendrá una única instancia del elemento repetido. Además, los conjuntos son estructuras de datos desordenadas, por lo que no podrás predecir el orden final de los elementos en el conjunto resultante al proporcionar una lista.

Procesamiento de iterables e iteradores

Los iteradores e iterables de Python son dos herramientas diferentes pero relacionadas que resultan útiles cuando necesitas iterar sobre un flujo de datos o una colección de datos. Los iteradores potencian y controlan el proceso de iteración, mientras que los iterables normalmente contienen datos que se pueden iterar sobre un valor a la vez.

Python tiene varias funciones integradas que puede utilizar para trabajar con iterables e iteradores. Aquí hay un resumen de estas funciones:

len()

Calcula la longitud de un objeto de tamaño.

reversed()

Construye un iterador inverso

sorted()

Crea una lista ordenada a partir de un iterable.

all()

Comprueba si todos los elementos de un iterable son verdaderos.

any()

Comprueba si algún elemento de un iterable es verdadero.

range()

Genera un rango de valores enteros.

enumerate()

Crea un iterador de tuplas que contienen índices y valores de un iterable.

slice()

Crea un objeto slice

zip()

Crea un iterador que agrega elementos de iterables.

iter()

Construye un objeto iterador

next()

Recupera el siguiente elemento de un iterador.

filter()

Filtra elementos de un iterable.

map()

Aplica una función a cada elemento de un iterable.

En las siguientes secciones, aprenderá sobre todas estas funciones integradas y cómo pueden resultar útiles al procesar iterables e iteradores en su código Python.

Determinar el número de artículos en un contenedor: len()

Una de las operaciones más comunes que realizará en colecciones es determinar la cantidad de elementos almacenados en una secuencia o colección existente. Para completar esta tarea, Python tiene la función len() incorporada.

La función len() toma un único argumento, que puede ser una secuencia, como una cadena, una tupla o una lista. También puede ser una colección, como un diccionario, un conjunto o un conjunto congelado. La función devuelve la longitud o el número de elementos del objeto de entrada.

A continuación se muestran algunos ejemplos del uso de len() con diferentes objetos:

>>> len("Python")
6

>>> len(("Jane Doe", 25, 1.75, "Canada"))
4

>>> len([1, 2, 3, 4, 5])
5

>>> len({"green", "red", "blue"})
3

>>> len({"name": "Jane", "age": 30, "country": "Canada"})
3

En el primer ejemplo, usa len() para obtener el número de caracteres en una cadena. Luego, usa la función para determinar la longitud de una tupla, una lista y un conjunto. Finalmente, usas len() con un diccionario como argumento. En este caso, obtiene la cantidad de pares clave-valor en el diccionario de entrada.

Tenga en cuenta que len() devuelve 0 cuando lo llama con contenedores vacíos:

>>> len("")
0
>>> len(())
0
>>> len([])
0

En estos ejemplos, utiliza len() con una cadena, tupla y una lista vacías. En todos los casos, obtienes 0 como resultado.

La función len() puede resultar útil en varias situaciones. Un ejemplo común es cuando necesitas calcular el promedio de una serie de valores numéricos:

>>> grades = [90, 97, 100, 87]

>>> sum(grades) / len(grades)
93.5

En este ejemplo, len() le proporciona el número de valores en la lista de calificaciones. Luego, utiliza este valor para calcular la calificación promedio.

Invertir y ordenar iterables: reversed() y sorted()

Invertir y ordenar los valores en un iterable es otra operación útil en programación. Debido a que estas operaciones son tan comunes, Python tiene funciones integradas para ellas. Cuando desee revertir un iterable, puede utilizar la función incorporada reversed().

La función reversed() toma un iterable como argumento y devuelve un iterador que produce los elementos en orden inverso:

>>> reversed([0, 1, 2, 3, 4, 5])
<list_reverseiterator object at 0x107062b30>

>>> list(reversed([0, 1, 2, 3, 4, 5]))
[5, 4, 3, 2, 1, 0]

En este ejemplo, llamas a reversed() con una lista de números. La función devuelve un iterador inverso. Para mostrar su contenido, puede utilizar la función list() para crear una lista a partir del iterador.

De manera similar, cuando necesite ordenar los valores en un iterable, puede usar la función sorted(). Esta función tiene la siguiente firma:

sorted(iterable, key=None, reverse=False)

El primer argumento es un objeto iterable, como una cadena, una lista, una tupla o un diccionario. Luego, tiene los argumentos key y reverse, que tienen los siguientes significados:

key

Especifica una función de un argumento que extrae una clave de comparación de cada elemento en el iterable de entrada.

reverse

Es un valor booleano que le permite ordenar los elementos en orden inverso si lo configura en True

Es importante tener en cuenta que el argumento key acepta objetos de función. En otras palabras, funciones sin paréntesis de llamada.

A continuación se muestran algunos ejemplos del uso de sorted() con diferentes contenedores integrados como argumentos:

>>> sorted("bdeac")
['a', 'b', 'c', 'd', 'e']

>>> sorted([4, 2, 7, 5, 1, 6, 3])
[1, 2, 3, 4, 5, 6, 7]

>>> sorted([4, 2, 7, 5, 1, 6, 3], reverse=True)
[7, 6, 5, 4, 3, 2, 1]

A diferencia de reversed(), la función sorted() siempre devuelve un objeto de lista en lugar de un iterador. En el primer ejemplo, utiliza una cadena como argumento para sorted() y obtiene una lista de caracteres en orden alfabético. Debajo del capó, Python usa el punto de código Unicode del carácter para comparar cadenas.

En el tercer ejemplo, establece el argumento reverse en True y obtiene la lista de números ordenados en orden inverso.

Finalmente, el argumento key puede resultar útil en varias situaciones. Un caso de uso común para este argumento es cuando necesita ordenar elementos que también son contenedores. A continuación se muestra un ejemplo de cómo ordenar una lista de tuplas de dos valores:

>>> points = [(1, 2), (3, 1), (4, 0), (2, 1)]

>>> sorted(points, key=lambda point: point[0])
[(1, 2), (2, 1), (3, 1), (4, 0)]

>>> sorted(points, key=lambda point: point[1])
[(4, 0), (3, 1), (2, 1), (1, 2)]

En la primera línea resaltada, utiliza una función lambda que toma un punto como argumento y devuelve su primera coordenada. Este ejemplo produciría el mismo resultado si llamas a sorted() sin key debido a la forma en que Python compara tuplas. En la segunda línea resaltada, la función lambda devuelve la segunda coordenada. Estas funciones proporcionan claves de comparación para los procesos de clasificación.

Entonces, en el primer ejemplo, ordenas los puntos por su primera coordenada, mientras que en el segundo ejemplo, ordenas los puntos por su segunda coordenada.

Determinar el valor de verdad de los elementos en Iterables: all() y any()

A veces, es posible que necesites determinar si todos los elementos de un iterable son verdaderos. Para hacer esto, Python tiene la función all() incorporada. En otras ocasiones, es posible que necesites averiguar si al menos un elemento de un iterable es verdadero. Para este propósito, Python tiene la función any() incorporada.

La firma de all() y any() se muestra a continuación:

all(iterable)

any(iterable)

Ambas funciones toman un iterable de objetos como argumento. La función all() devuelve True cuando todos los elementos del iterable de entrada son verdaderos y False cuando al menos un elemento es falso.

A continuación se muestran algunos ejemplos de cómo funciona all():

>>> all([1, 2, 3, 4])
True
>>> all([1, 2, 3, 4, 0])
False

>>> all(["Hello", ""])
False
>>> all(["Hello", "World"])
True

En los dos primeros ejemplos, utiliza all() con una lista de números. La primera lista contiene valores enteros diferentes de 0, que Python considera verdaderos. Entonces, all() devuelve Verdadero. Luego, en el segundo ejemplo, agrega un 0 a la lista y all() devuelve False porque este valor se considera falso en Python. .

En los dos últimos ejemplos, utiliza una lista de cadenas. Python considera falsa una cadena vacía, por lo que all() devuelve False. Finalmente, pasa una lista con dos cadenas no vacías y all() vuelve a ejecutar True.

Puede encontrar varios casos de uso para all() en código del mundo real. Quizás pasar una expresión generadora como argumento a all() sea una de las formas más poderosas de usar la función. Por ejemplo, digamos que desea determinar si todos los números de una lista están en un intervalo determinado. En esta situación, puede utilizar all() como en el siguiente código:

>>> numbers = [10, 5, 6, 4, 7, 8, 18]

>>> # Between 0 and 10
>>> all(0 <= x <= 10 for x in numbers)
False

>>> # Between 0 and 20
>>> all(0 <= x <= 20 for x in numbers)
True

En la primera línea resaltada, utiliza una expresión generadora como argumento para all(). El generador comprueba si los números están entre 0 y 10 y genera valores booleanos según el resultado de la condición.

La función all() comprueba si todos los valores generados son True. Si ese es el caso, obtendrás True. Por otro lado, si al menos uno de los valores generados es False, entonces obtendrás False.

A diferencia de all(), la función any() devuelve True si al menos un elemento es verdadero y False. > si todos los elementos son falsos. Por lo tanto, puedes usar any() cuando necesites determinar si al menos un elemento en un iterable es verdadero.

Aquí hay un par de ejemplos del uso de any() en Python:

>>> any([0, 0.0, False, "", []])
False

>>> any([0, 0.0, False, "", [], 42])
True

En el primer ejemplo, todos los objetos en la lista de entrada son falsos según las reglas internas de Python para determinar el valor de verdad de un objeto. Como todos los objetos son falsos, any() devuelve False. En el segundo ejemplo, agrega el número 42 al final de la lista de entrada. Debido a que 42 es verdadero, any() devuelve True.

Nuevamente, al igual que all(), la función any() puede brillar cuando la usas con una expresión generadora que verifica alguna condición. Por ejemplo, digamos que desea saber si al menos una letra de un texto determinado está en mayúscula. En esta situación, puede hacer algo como lo siguiente:

>>> any(letter.isupper() for letter in  "hello, world!")
False

>>> any(letter.isupper() for letter in  "Hello, World!")
True

En estos ejemplos, utiliza el método str.isupper() para determinar si una letra está en mayúscula. Este método devuelve True o False, por lo que obtienes un iterable de valores booleanos. El trabajo de any() es determinar si alguno de estos valores es verdadero.

En el primer ejemplo, no tienes letras mayúsculas, por lo que any() devuelve False. En el segundo ejemplo, tiene una H mayúscula, por lo que any() devuelve True.

Creando rangos de valores enteros: range()

A veces, es necesario crear rangos numéricos para representar una serie de valores enteros en un intervalo determinado. Por lo general, necesita que los números sean consecutivos, pero es posible que también desee que no sean secuenciales.

Por ejemplo, puedes crear un rango de valores que contenga todos los dígitos usando una lista de Python:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Este enfoque funcionará si su rango es relativamente pequeño como el de este ejemplo. Sin embargo, ¿qué pasa si su rango necesita tener un millón de valores? Construir ese tipo de rango con una lista sería una tarea difícil. Hay una mejor manera.

Python tiene la función range() incorporada para facilitar la creación de rangos numéricos:

>>> range(10)
range(0, 10)

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> range(1_000_000)
range(0, 1000000)

>>> list(range(1_000_000))
[1, 2, 3,
 ...,
 999998, 999999]

La función range() devuelve un objeto range en lugar de una lista. Si desea verificar los valores en un rango concreto, puede envolverlo en una llamada a list(), como lo hizo en el segundo ejemplo. Ahora tienes una lista de dígitos.

En el tercer ejemplo, crea un rango con un millón de números enteros desde 0 hasta 999999. Si quieres verlo en acción, pásalo a list(). ¡Obtendrás una ventana de terminal llena de números!

La función incorporada range() tiene las siguientes firmas:

range(stop)
range(start, stop, step=1)

Tiene una variante de la función de un argumento y de tres argumentos. Esto es lo que significa cada argumento:

start

Mantiene el valor inicial en el rango. El valor predeterminado es 0 y está incluido en el rango.

stop

Contiene el valor en el que se detiene el rango. Es un argumento obligatorio y su valor no está incluido en el rango.

step

Mantiene el paso a través de sucesivos valores. Es un argumento opcional cuyo valor predeterminado es 1.

Hasta ahora, has utilizado la variación de un argumento, que comienza el rango en 0 y construye un rango de números enteros consecutivos. Aquí hay un par de ejemplos que muestran la variación de tres argumentos:

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> list(range(10, 101, 10))
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

Tenga en cuenta que en la variación de tres argumentos, el tercer argumento es opcional. Esto significa que puede llamar a la función con dos argumentos y confiar en el valor predeterminado de step, que es 1. Eso es lo que haces en el primer ejemplo, que construye un rango de números enteros consecutivos desde 1 hasta 11. Nuevamente, 11 no está incluido en el rango final porque es el valor en el que range() deja de emitir valores.

En el segundo ejemplo, crea un rango que comienza en 10 y llega hasta 101 con un paso de 10.

También puede utilizar valores negativos para los argumentos de range(). Un caso de uso común para esto es cuando necesita crear rangos de números negativos y rangos que cuentan hacia atrás:

>>> list(range(-10, 0))
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1]

>>> list(range(100, 0, -10))
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]

En este ejemplo, el primer rango contiene números negativos consecutivos desde -10 hasta 0. El segundo rango cuenta hacia atrás desde 100 hasta 0 con un paso de -10.

Los rangos pueden resultar útiles en varias situaciones. Un buen caso de uso para un rango es cuando necesita realizar una operación un número determinado de veces:

>>> for _ in range(3):
...     print("Beep")
...
Beep
Beep
Beep

Este bucle se ejecuta tres veces e imprime un mensaje en la pantalla. Sin embargo, el bucle no necesita utilizar la variable de bucle en su cuerpo. Por lo tanto, se utiliza un guión bajo para indicar que se trata de una variable desechable.

Un mal uso común de los objetos range en Python es usarlos como una forma de recorrer un iterable existente:

>>> letters = ["a", "b", "c", "d"]

>>> for index in range(len(letters)):
...     print(index, letters[index])
...
0 a
1 b
2 c
3 d

Este tipo de bucle for no es el mejor ejemplo de una construcción Pythonic. Utiliza range() para recorrer las letras con índices numéricos. En la siguiente sección, aprenderá sobre la forma Pythonic de escribir este tipo de bucle.

Enumerar elementos en bucles: enumerate()

Un bucle Pythonic for itera sobre los elementos en un iterable sin considerar el índice en el que se encuentra un elemento determinado o el orden en el que se procesaron los elementos. Descubrirá que la mayoría de los bucles for de Python se verán así:

>>> colors = ["red", "orange", "yellow", "green"]

>>> for color in colors:
...     print(color)
...
red
orange
yellow
green

Esta forma de escribir bucles en Python es explícita e intuitiva, lo que afecta profundamente la legibilidad del bucle. Sin embargo, a veces necesitarás una forma de acceder al índice donde se encuentra un elemento determinado en el iterable de entrada.

A menudo, cuando comienzas con Python y necesitas acceder a índices en un bucle, puedes terminar escribiendo un bucle como el siguiente:

>>> for index in range(len(colors)):
...     print(index, colors[index])
...
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet

Este tipo de bucle funciona. Sin embargo, no es lo que se puede llamar un bucle Pythonic. Utiliza un par de trucos para imitar de alguna manera la iteración sobre índices. Python ofrece una mejor herramienta para hacer esto, y esa herramienta es la función incorporada enumerate().

Así es como escribirás el bucle anterior usando la función enumerate():

>>> for index, color in enumerate(colors):
...     print(index, color)
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet

Este bucle es legible y explícito. La función enumerate() toma un iterable como argumento y genera tuplas de dos elementos que contienen un índice entero y el elemento asociado.

Aquí está la firma de la función:

enumerate(iterable, start=0)

El primer argumento es un objeto iterable. El segundo argumento, start, le brinda la opción de definir un valor inicial para la enumeración. El valor predeterminado es 0 porque ese es el valor inicial habitual de los índices en la programación. Sin embargo, en algunas situaciones, puede resultar conveniente utilizar un punto de partida diferente, como 1.

Para ilustrar cómo utilizar el argumento start, digamos que está creando una aplicación de interfaz de usuario basada en texto (TUI) y desea mostrar un menú con algunas opciones. Las opciones deben tener un número asociado para que el usuario pueda elegir la acción deseada. En esta situación, puede utilizar enumerate() como en el siguiente código:

>>> def list_menu(options):
...     print("Main Menu:")
...     for index, option in enumerate(options, start=1):
...         print(f"{index}. {option}")
...

>>> list_menu(["Open", "Save", "Settings", "Quit"])
Main Menu:
1. Open
2. Save
3. Settings
4. Quit

Desde la perspectiva del usuario final, comenzar la lista del menú en 1 es el camino natural a seguir. Puede lograr este efecto configurando start en 1 en la llamada a enumerate(). Ahora, el menú comienza en 1 en lugar de 0.

Extracción de sectores o porciones de secuencias: slice()

Cuando trabaja con Python, es posible que necesite extraer una parte o un segmento de una secuencia existente, como una cadena, una lista o una tupla. Para hacer esto, normalmente usa el operador de corte ([]). Sin embargo, también puede utilizar la función incorporada slice(). La función slice() tiene las siguientes firmas:

slice(stop)
slice(start, stop, step=None)

La función slice() devuelve un objeto slice que representa el conjunto de índices especificados por range(start, stop, step). Los argumentos aquí tienen un significado similar al de la función range():

start

Mantiene el valor inicial en el sector. El valor predeterminado es Ninguno, que indica el inicio de la secuencia.

stop

Contiene el valor en el que se detiene el corte. Es un argumento obligatorio y su valor no está incluido en el segmento. Cuando se establece en Ninguno, significa el final de la secuencia (len(sequence)).

step

Mantiene el paso a través de sucesivos valores. Es un argumento opcional cuyo valor predeterminado es Ninguno, lo que significa un paso de 1.

Los sectores no representan rangos numéricos sino conjuntos de índices. Puede utilizar estos índices para extraer una parte de una lista. A continuación se muestran algunos ejemplos:

>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> even = numbers[slice(1, None, 2)]
>>> even
[2, 4, 6, 8]

>>> odd = numbers[slice(None, None, 2)]
>>> odd
[1, 3, 5, 7, 9]

En el primer segmento, comienza en 1 y sube hasta el final de la lista con un paso de 2. Este segmento le brinda una lista de números pares. En el segundo ejemplo, comienza al principio de la lista y sube hasta el final de la lista con un paso de 2. Con este segmento, obtienes una lista de números impares.

Tenga en cuenta que en la mayoría del código Python, no verá ni utilizará slice() como lo hizo en el ejemplo anterior. En la mayoría de los casos, utilizará el operador de división, [start:stop:step]. Así es como se ve esto con este operador:

>>> even = numbers[1::2]
>>> even
[2, 4, 6, 8]

>>> odd = numbers[::2]
>>> odd
[1, 3, 5, 7, 9]

En estos ejemplos, utiliza el operador de división para obtener los números pares e impares de su lista original. Tenga en cuenta que omitir un índice determinado hace que ese índice dependa de su valor predeterminado. Por ejemplo, cuando no proporciona un índice start, el valor predeterminado es el principio de la lista.

Comprimir iterables para iteración paralela: zip()

La función zip() incorporada de Python le permite iterar sobre múltiples iterables en paralelo. Esta función crea un iterador que agregará elementos de dos o más iterables, generando tuplas de valores.

Aquí está la firma de la función zip() incorporada:

zip(*iterables, strict=False)

Esta función toma un número indefinido de iterables como argumentos y produce tuplas de elementos bajo demanda. Las tuplas contendrán un elemento de cada entrada iterable, lo que las hace ideales para iteraciones paralelas.

Aquí hay algunos ejemplos rápidos:

>>> letters = ["a", "b", "c"]
>>> numbers = [1, 2, 3]
>>> operators = ["*", "/", "+"]

>>> for characters in zip(letters, numbers, operators):
...     print(characters)
...
('a', 1, '*')
('b', 2, '/')
('c', 3, '+')

>>> for l, n, o in zip(letters, numbers, operators):
...     print(f"{l} {n} {o}")
...
a 1 *
b 2 /
c 3 +

En el primer bucle, utiliza una única variable de bucle para almacenar cada tupla que obtiene de zip(). La primera tupla contiene los primeros elementos de cada iterable de entrada. La segunda tupla contiene los segundos elementos, y así sucesivamente. En el segundo bucle, utiliza tres variables de bucle para descomprimir los elementos de cada tupla generada.

Un buen caso de uso de zip() es cuando tienes dos listas y quieres crear un diccionario a partir de ellas. Considere el siguiente ejemplo:

>>> keys = ["name", "age", "country"]
>>> values = ["Jane", "30", "Canada"]

>>> dict(zip(keys, values))
{'name': 'Jane', 'age': '30', 'country': 'Canada'}

En este ejemplo, combina dos listas existentes usando zip() y pasa las tuplas resultantes a la función dict() para crear un diccionario.

El argumento strict de zip() se agregó en Python 3.10 y es un argumento de solo palabras clave que proporciona una forma segura de manejar iterables de longitud desigual. El valor predeterminado del argumento es False, lo que significa que zip() solo generará tantas tuplas como elementos en el iterable más corto.

Si establece strict en True, obtendrá una excepción ValueError cuando los iterables de entrada no tengan la misma longitud:

>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

>>> list(zip(range(5), range(100), strict=True))
Traceback (most recent call last):
    ...
ValueError: zip() argument 2 is longer than argument 1

En el primer argumento, confía en el valor predeterminado de strict y obtiene cinco tuplas porque el rango más corto solo tiene cinco valores. En el segundo ejemplo, establece strict en True. Esta vez, obtiene un error porque los rangos de entrada no tienen la misma cantidad de valores.

Construyendo y consumiendo iteradores: iter() y next()

En Python, los iteradores implementan el patrón de diseño de iteradores, que le permite atravesar un contenedor y acceder a sus elementos. El patrón iterador desacopla los algoritmos de iteración de los contenedores, como listas, tuplas, diccionarios y conjuntos.

Python tiene dos funciones integradas que pueden ayudarte cuando trabajas con iteradores. La función iter() le permite crear un iterador a partir de un iterable, y la función next() le permite consumir un iterador un elemento a la vez.

Considere el siguiente ejemplo de juguete:

>>> colors = ["red", "orange", "yellow", "green"]

>>> colors_it = iter(colors)
>>> colors_it
<list_iterator object at 0x10566a170>

>>> next(colors_it)
'red'
>>> next(colors_it)
'orange'
>>> next(colors_it)
'yellow'
>>> next(colors_it)
'green'
>>> next(colors_it)
Traceback (most recent call last):
    ...
StopIteration

En este ejemplo, utiliza la función iter() para crear un objeto iterador a partir de una lista de colores existente. A diferencia de los iterables, los iteradores admiten la función next(). Cuando llamas a esta función con un iterador como argumento, obtienes el primer elemento en la primera llamada. Cuando vuelves a llamar a la función, obtienes el segundo elemento, y así sucesivamente.

Cuando recorre todos los elementos del iterador, next() genera una excepción StopIteration. Python utiliza internamente esta excepción para detener el proceso de iteración en un bucle for o una comprensión. Tenga en cuenta que puede atravesar un iterador sólo una vez. Después de eso, el iterador se agotará o se consumirá.

Aquí están las firmas de iter():

iter(iterable)
iter(object, sentinel)

En la primera firma, iterable representa cualquier tipo iterable. En la segunda firma, el argumento object debe ser invocable. Ya has visto la primera firma en acción. Ahora es el momento de echar un vistazo rápido a la segunda firma.

Para ilustrar con un ejemplo, supongamos que está trabajando en una aplicación de interfaz de línea de comandos (CLI) y desea tomar la entrada del usuario hasta que ingrese la palabra "done". Así es como puedes hacer esto usando la función iter():

>>> def read_user_input():
...     print("Enter word (type 'done' to finish):")
...     for word in iter(input, "done"):
...         print(f"Processing word: '{word}'")
...

>>> read_user_input()
Enter word (type 'done' to finish):
Python
Processing word: 'Python'
Programming
Processing word: 'Programming'
Iterators
Processing word: 'Iterators'
done

En la línea resaltada, usa iter() con dos argumentos. Para el primer argumento, utiliza la función incorporada input(), que le permite recibir información del usuario. Tenga en cuenta que no llama a la función sino que la pasa como un objeto de función.

Luego, tienes la palabra "done", que funciona como centinela. En otras palabras, iter() llamará a input() por usted y generará una excepción StopIteration si su valor de retorno coincide con la palabra centinela.

Cuando llamas a la función, se te pide que ingreses una palabra, luego el código procesa la palabra y te permite ingresar otra palabra. Estas acciones se repiten hasta que ingresas tu centinela, la palabra "done".

Cuando se trata de la función next(), también tendrás dos firmas diferentes que se parecen a esta:

next(iterator)
next(iterator, default)

Nuevamente, has visto cómo usar next() con un iterable como argumento único. Ahora puedes concentrarte en usar la segunda firma. En este caso, tiene un segundo argumento llamado default. Este argumento le permite proporcionar el valor que desea devolver cuando el iterable de entrada se agote o se acaben sus datos:

>>> count_down = iter([3, 2, 1])

>>> next(count_down, 0)
3
>>> next(count_down, 0)
2
>>> next(count_down, 0)
1
>>> next(count_down, 0)
0
>>> next(count_down, 0)
0

En este ejemplo, crea un iterador a partir de una lista de números. Luego, usa next() para consumir el iterador un número a la vez. Después de que next() haya consumido todo el iterador, obtendrás 0 como resultado porque ese es el valor que pasaste a default, el segundo argumento posicional. . Las llamadas sucesivas a la función también devolverán 0.

Iterables de filtrado y mapeo: filter() y map()

¿Has oído hablar de la programación funcional? Es un paradigma de programación en el que un programa está dominado por llamadas a funciones puras, que son funciones cuyos valores de salida dependen únicamente de sus valores de entrada sin ningún efecto secundario observable.

Python no es lo que se podría llamar un lenguaje de programación funcional. Sin embargo, tiene un par de funciones integradas que son herramientas funcionales clásicas. Estas herramientas son las funciones integradas filter() y map().

Puede utilizar la función filter() para extraer valores de iterables, lo que se conoce como operación de filtrado.

La firma de filter() se parece a esto:

filter(function, iterable)

El primer argumento, function, debe ser una función de un solo argumento, mientras que el segundo argumento puede ser cualquier iterable de Python. Aquí hay una breve descripción de estos argumentos:

function

Un predicado o función con valor booleano que acepta un solo argumento.

iterable

Un iterable de Python

El argumento function es una función de decisión, también conocida como función de filtrado. Proporciona los criterios para decidir si se mantiene un valor determinado.

En la práctica, filter() aplica function a todos los elementos en iterable. Luego, crea un iterador que produce solo los elementos que cumplen con los criterios que verifica function. En otras palabras, produce los elementos que hacen que function devuelva True.

La función filter() le permite procesar iterables sin un bucle formal. A continuación se muestra un ejemplo del uso de filter() para extraer números pares de una lista de valores:

>>> numbers = [1, 3, 10, 45, 6, 50]

>>> list(filter(lambda n: n % 2 == 0, numbers))
[10, 6, 50]

En este ejemplo, la función lambda toma un número entero y devuelve True si el valor de entrada es un número par y False en caso contrario. La llamada a filter() aplica esta función lambda a los valores en numbers y filtra los números impares, devolviendo los números pares. Tenga en cuenta que utiliza la función list() para crear una lista a partir del iterador que devuelve filter().

La función map() es otra herramienta común en la programación funcional. Esta función le permite aplicar una función de transformación a todos los valores en un iterable.

El map() de Python tiene la siguiente firma:

map(function, iterable, *iterables)

La función map() aplica function a cada elemento en iterable en un bucle y devuelve un nuevo iterador que produce elementos transformados a pedido.

Aquí hay un resumen de los argumentos y sus significados:

function

Una función de Python que toma una cantidad de argumentos igual a la cantidad de entradas. iterables

iterable

Un argumento obligatorio que puede contener cualquier iterable de Python.

*iterables

Un número variable de iterables de Python.

El argumento function es lo que se llama una función de transformación. Aplica una transformación específica a sus argumentos y devuelve un valor transformado.

Para ilustrar cómo funciona map(), digamos que tiene dos listas. La primera lista contiene una serie de valores que desea utilizar como base en los cálculos de potencia. La segunda lista contiene los exponentes que deseas aplicar a cada base. Puedes usar la función map() para procesar estas listas y obtener un iterador de poderes:

>>> bases = [8, 5, 2]
>>> exponents = [2, 3, 4]

>>> list(map(pow, bases, exponents))
[64, 125, 16]

En este ejemplo, utiliza la función incorporada pow() como primer argumento de map(). Como ya aprendiste, pow() toma una base y un exponente como argumentos y devuelve la potencia. Luego, pasas las bases y los exponentes a map() para que calcule las potencias deseadas.

Procesamiento de entrada y salida

Si necesita recibir información del usuario o archivos y presentar la salida al usuario, entonces debe saber que el lenguaje tiene algunas funciones integradas que pueden ayudarlo con estas tareas:

input()

Lee la entrada de la consola.

open()

Abre un archivo y proporciona acceso a un objeto de archivo.

print()

Imprime en una secuencia de texto o en la consola.

format()

Convierte un valor en una representación formateada

En las siguientes secciones, profundizará en el uso de estas funciones para procesar operaciones de entrada y salida en su código Python.

Aceptar información del usuario: input()

Recibir información de sus usuarios es una operación común en aplicaciones CLI y de interfaz basada en texto (TUI). Python tiene una función incorporada que está específicamente dirigida a este tipo de operación. La función se llama convenientemente input().

La función incorporada input() lee la entrada del usuario y la toma como una cadena. Aquí está la firma de la función:

input([prompt])

Los corchetes alrededor de prompt son una indicación de que este argumento es opcional. Este argumento le permite proporcionar un mensaje para solicitar al usuario la entrada requerida o deseada.

Como ejemplo del uso de input(), digamos que desea crear un juego de adivinanzas de números. El juego pedirá al usuario que ingrese un número del 1 al 10 y verifique si el valor ingresado coincide con un número secreto.

Aquí está el código del juego:

from random import randint

LOW, HIGH = 1, 10

secret_number = randint(LOW, HIGH)
clue = ""

while True:
    guess = input(f"Guess a number between {LOW} and {HIGH} {clue} ")
    number = int(guess)
    if number > secret_number:
        clue = f"(less than {number})"
    elif number < secret_number:
        clue = f"(greater than {number})"
    else:
        break

print(f"You guessed it! The secret number is {number}")

En este código, define un bucle infinito en el que solicita al usuario que adivine ingresando un número entre 1 y 10. La primera línea del bucle es una llamada al input() integrado. función. Ha utilizado un mensaje descriptivo para informar a los usuarios qué hacer.

Continúe y ejecute el script desde su línea de comando para probarlo:

$ python guess.py
Guess a number between 1 and 10  2
Guess a number between 1 and 10 (greater than 2) 3
Guess a number between 1 and 10 (greater than 3) 8
Guess a number between 1 and 10 (less than 8) 6
You guessed it! The secret number is 6

¡Fresco! Su juego le pide al usuario que ingrese un número, lo compara con el número secreto y les permite saber cuándo adivinan correctamente. La función input() juega un papel central en el flujo del juego, permitiéndote obtener y procesar la entrada del usuario.

Abrir archivos: open()

Leer y escribir en archivos son tareas de programación comunes. En Python, puede utilizar la función incorporada open() para estos fines. Normalmente se utiliza la función open() en una declaración with.

Como ejemplo rápido, supongamos que tiene un archivo de texto con el siguiente contenido:

apple
banana
cherry
orange
mango

Quiere abrir el archivo y leer su contenido mientras lo imprime en la pantalla. Para hacer esto, puede utilizar el siguiente código:

>>> with open("fruits.txt") as file:
...     print(file.read())
...
apple
banana
cherry
orange
mango

En esta instrucción with, llamas a open() con el nombre del archivo como argumento. Esta llamada abre el archivo para su lectura. La función open() devuelve un objeto de archivo, que la instrucción with asigna a las variables file con as especificador.

La función open() tiene la siguiente firma:

open(
    file,
    mode="r",
    buffering=-1,
    encoding=None,
    errors=None,
    newline=None,
    closefd=True,
    opener=None,
)

La función puede tomar hasta ocho argumentos. El primer argumento, archivo, es el único argumento obligatorio. El resto de argumentos son opcionales. Aquí hay un resumen del significado de los argumentos:

Argument Description Comment
file A path-like object holding the path to the target file It’s a required argument.
mode A string that specifies the mode in which you want to open the file It defaults to "r", which is the reading mode. You’ll learn about the available modes in a moment.
buffering An integer that sets the buffering policy You can pass 0 to switch buffering off, which is only possible in binary mode. You can use 1 to select line buffering, which is only usable in text mode. Finally, you can use an integer greater than 1 to indicate the size in bytes of a fixed-size chunk buffer.
encoding The name of the encoding used to decode or encode the file You can only use this argument in text mode.
errors A string that specifies how encoding and decoding errors are to be handled You can only use this argument in text mode. It can take one of the following values: "strict", "ignore", "replace", "surrogateescape", "xmlcharrefreplace", "backslashreplace", or "namereplace". These values have similar meanings to those you learned in the section about the ord() and chr() functions.
newline A string that determines how to parse newline characters from the stream It can be None, "", "\n", "\r", or "\r\n".
closefd A Boolean value that defines whether you want to close a file descriptor It can be False when you provide a file descriptor instead of a filename and want the descriptor to remain open when the file is closed. Otherwise, it must be True.
opener A callable that you use as a custom opener for the target file The opener must return an open file descriptor.

En este tutorial, no cubrirá todos estos argumentos. En su lugar, aprenderá acerca de dos de los argumentos más utilizados, que son mode y encoding.

Aquí hay una lista de valores mode permitidos:

"r"

Abre el archivo para lectura y es el valor predeterminado.

"w"

Abre el archivo para escribir, truncando el archivo primero

"x"

Abre el archivo para creación exclusiva, fallando si el archivo ya existe

"a"

Abre el archivo para escribir, agregando los nuevos datos al final del archivo si ya existe.

"b"

Abre el archivo en modo binario.

"t"

Abre el archivo en modo texto, que es el modo predeterminado.

"+"

Abre el archivo para actualizar, lo que permite operaciones de lectura y escritura.

En esta tabla, los valores "b" y "t" definen dos modos genéricos para archivos binarios y de texto, respectivamente. Puede combinar estos dos modos con otros modos. Por ejemplo, el modo "wb" le permite escribir datos binarios en un archivo, el modo "rt" le permite leer datos basados en texto de un archivo y pronto.

Tenga en cuenta que "t" es el modo predeterminado. Entonces, si configura el modo en "w", Python asume que desea escribir texto en el archivo de destino.

Aquí hay un fragmento de código que escribe texto en un archivo en su directorio de trabajo:

>>> with open("hello.txt", "w") as file:
...     file.write("Hello, World!")
...
13

En este ejemplo, abre un archivo llamado hello.txt para poder escribir texto en él. En el bloque de código de la instrucción with, llama al método .write() en el objeto de archivo para escribir algo de texto. Tenga en cuenta que el método devuelve el número de bytes escritos. Por eso aparece 13 en la pantalla.

Después de ejecutar el código, tendrá el archivo hello.txt en su directorio de trabajo. Continúe y ábralo para comprobar su contenido.

Puede experimentar con otros modos y tener una idea de cómo funcionan para poder usarlos de forma segura en su código. ¡Toma esto como un ejercicio práctico!

Usar el argumento encoding es otro requisito típico cuando se trabaja con archivos de texto. En esta situación, se recomienda indicar explícitamente la codificación de texto que está utilizando en su código. La codificación UTF-8 es un ejemplo común de un valor que pasarías a encoding:

>>> with open("hello.txt", "w", encoding="utf-8") as file:
...     file.write("Hello, Pythonista!")
...
13

>>> with open("hello.txt", "r", encoding="utf-8") as file:
...     print(file.read())
...
Hello, Pythonista!

En este ejemplo, utiliza la codificación UTF-8 para escribir y leer desde un archivo de texto. Tenga en cuenta que debe utilizar explícitamente el nombre del argumento para proporcionar el valor de codificación. Esto se debe a que el siguiente argumento en la lista es buffering en lugar de encoding, y si no usa el nombre explícito, obtendrá un TypeError excepción.

Imprimir texto en la pantalla u otra salida: print()

Otro requisito común que surge cuando se crean aplicaciones CLI o TUI es mostrar información en la pantalla para informar al usuario sobre el estado de la aplicación. En este caso, puede utilizar la función print() incorporada, que es una herramienta fundamental en la programación de Python.

La función print() tiene la siguiente firma:

print(*objects, sep=" ", end="\n", file=None, flush=False)

Llamar a print() imprimirá los objetos de entrada en la pantalla de forma predeterminada. Puede utilizar el resto de los argumentos para modificar el funcionamiento de la función. Aquí hay un resumen de los argumentos y su significado:

*objects

Un número arbitrario de objetos Python

sep

La cadena que desea utilizar para separar los objetos de entrada entre sí

end

La cadena que se utilizará después del último objeto de entrada.

file

El objeto de archivo abierto donde desea escribir los objetos de entrada

flush

Un valor booleano que define si desea vaciar el búfer de salida.

Cuando llamas a print(), toma los objetos de entrada, los convierte en cadenas, los une usando sep y agrega end. . Si llama a print() sin argumentos, entonces imprime end. Tenga en cuenta que los argumentos sep, end, file y flush son argumentos de palabras clave.

A continuación se muestran algunos ejemplos de cómo utilizar la función print():

>>> print()

>>> print("Hello")
Hello
>>> print("Hello", "Pythonista!")
Hello Pythonista!
>>> print("Hello", "Pythonista!", sep="\t")
Hello    Pythonista!
>>> print("Hello", "Pythonista!", sep="\t", end=" 👋\n")
Hello    Pythonista! 👋

Cuando llamas a print() sin argumentos, end se imprime en la pantalla. Este argumento por defecto es un carácter de nueva línea, así que eso es lo que obtienes. Con un objeto como argumento, el objeto se imprime, seguido de un carácter de nueva línea. Con varios objetos como argumentos, los argumentos se unen mediante sep y se agrega una nueva línea al final.

También puedes modificar el valor de end y hacer que Python imprima algo diferente al final de tu salida.

El argumento file por defecto es la salida estándar, que es su pantalla. La secuencia sys.stdout proporciona este valor predeterminado. Sin embargo, puede redirigir la salida a un objeto de archivo de su preferencia:

>>> with open("hello.txt", mode="w") as text_file:
...     print("Hello, World!", file=text_file)
...

Este fragmento de código anulará el archivo hello.txt existente de la sección anterior y escribirá la frase "Hello, World!" en él.

Finalmente, tiene el argumento flush que tiene que ver con el almacenamiento en búfer de datos. De forma predeterminada, Python almacena las llamadas a print() en un búfer de datos RAM. Esto permite a Python realizar menos llamadas al sistema para operaciones de escritura agrupando caracteres en el búfer y escribiéndolos todos a la vez con una sola llamada al sistema.

Puede establecer el argumento flush en True si desea que la salida de su código se muestre en tiempo real. Si mantiene flush en su valor predeterminado de False, entonces Python almacenará en el buffer la salida, y esa salida solo se mostrará una vez que el buffer de datos esté lleno o cuando finalice su programa. ejecución.

Un buen ejemplo del uso de flush es cuando necesita crear una barra de progreso para una aplicación CLI. Considere la siguiente función:

def progress(percent=0, width=30):
    end = "" if percent < 100 else "\n"
    left = width * percent // 100
    right = width - left
    print(
        "\r[",
        "#" * left,
        " " * right,
        "]",
        f" {percent:.0f}%",
        sep="",
        end=end,
        flush=True,
    )

Esta función genera una barra de progreso horizontal aprovechando el argumento flush. Así es como puedes usarlo en tu código:

>>> from time import sleep
>>> from progress import progress

>>> for percent in range(101):
...     sleep(0.2)
...     progress(percent)
...
[###########                   ] 38%

Este bucle llama a progress() con valores de progreso hipotéticos sucesivos. El resultado de cada llamada se vacía y la barra de progreso se muestra en la misma línea.

Formato de cadenas: format()

Python tiene un par de herramientas útiles para la interpolación y el formato de cadenas, incluidas f-strings y el método str.format(). Estas herramientas aprovechan el minilenguaje de formato de cadenas de Python, que le permite formatear bien sus cadenas utilizando una sintaxis dedicada.

La función incorporada format() es otra herramienta que puede utilizar para formatear valores. La función tiene la siguiente firma:

format(value, format_spec="")

La función convierte value en una representación formateada. Para definir el formato deseado, puede utilizar el argumento format_spec, que acepta una cadena que sigue la sintaxis definida en el minilenguaje de formato de cadenas. El argumento format_spec por defecto es una cadena vacía, lo que hace que la función devuelva el valor tal como se pasó.

Considere los siguientes ejemplos de uso de la función format():

>>> import math
>>> from datetime import datetime

>>> format(math.pi, ".4f")  # Four decimal places
'3.1416'

>>> format(math.pi, "e")  # In scientific notation
'3.141593e+00'

>>> format(1000000, ",.2f")  # Thousand separators
'1,000,000.00'

>>> format("Header", "=^30")  # Centered and filled
'============Header============'

>>> format(datetime.now(), "%a %b %d, %Y")  # Date
'Mon Jul 1, 2024'

En estos ejemplos, ha utilizado varios especificadores de formato diferentes. El especificador ".4f" formatea el valor de entrada como un número de punto flotante con cuatro decimales. El especificador "e" le permite formatear el valor de entrada utilizando notación científica.

Con el especificador de formato ",.2f", puede formatear un número usando comas como separadores de miles y con dos decimales, que es un formato apropiado para valores de moneda. Luego, usa el especificador "=^30" para formatear la cadena "Header" centrada en un ancho de 30 caracteres usando el signo igual. como personaje de relleno. Finalmente, usas "%a %b %d, %Y" para formatear una fecha.

Trabajar con clases, objetos y atributos

Python admite programación orientada a objetos (OOP) con clases, tipos, herencia y muchas otras características relacionadas. En Python todo es un objeto. Entonces, el paradigma de programación orientada a objetos es fundamental para el lenguaje mismo.

Tendrás varias funciones integradas que te ayudarán con diferentes tareas relacionadas con clases, tipos, atributos, métodos, herencia y otros conceptos relacionados con la programación orientada a objetos.

Aquí hay un resumen de las funciones integradas relacionadas con la programación orientada a objetos de Python:

property()

Devuelve el valor de una propiedad de una clase.

classmethod()

Devuelve un método de clase

staticmethod()

Devuelve un método estático

getattr()

Devuelve el valor de un atributo con nombre de un objeto.

setattr()

Establece el valor de un atributo con nombre de un objeto.

delattr()

Elimina un atributo de un objeto.

hasattr()

Devuelve True si un objeto tiene un atributo determinado

type()

Devuelve el tipo de un objeto o permite crear nuevas clases dinámicamente

isinstance()

Determina si un objeto es una instancia de una clase determinada.

issubclass()

Determina si una clase es una subclase de una clase determinada.

callable()

Devuelve True si un objeto parece ser invocable

super()

Devuelve un objeto proxy que delega llamadas a métodos a una clase principal o hermana

object()

Crea un nuevo objeto sin características

En las siguientes secciones, aprenderá sobre todas estas funciones y cómo usarlas en su código Python orientado a objetos.

Propiedades del edificio: property()

La función property() incorporada de Python le permite crear atributos administrados en sus clases. Los atributos administrados, también conocidos como propiedades, tienen un valor asociado y una implementación interna o comportamiento similar a una función.

Para ilustrar esto con un ejemplo, digamos que desea crear una clase Point. En Python, comenzarás con algo como lo siguiente:

>>> class Point:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...

>>> point = Point(42, 21)

>>> point.x
42
>>> point.y
21

>>> point.x = 0
>>> point.x
0

En esta clase, define dos atributos, .x y .y, para representar las coordenadas del punto. Puede acceder y actualizar los atributos directamente utilizando la notación de puntos. Entonces, de ahora en adelante, ambos atributos son parte de la API pública de su clase.

Ahora, digamos que necesita agregar alguna lógica de validación además de .x y .y. Por ejemplo, es posible que necesite validar los valores de entrada para ambos atributos. ¿Cómo harías eso? En lenguajes de programación como Java o C++, usarías los métodos getter y setter, que traducidos a Python pueden verse así:

class Point:
    def __init__(self, x, y):
        self.set_x(x)
        self.set_y(y)

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = self.validate(x)

    def get_y(self):
        return self._y

    def set_y(self, y):
        self._y = self.validate(y)

    def validate(self, value):
        if not isinstance(value, int | float):
            raise ValueError("coordinates must be numbers")
        return value

En esta nueva implementación de Point, ha convertido .x y .y en atributos no públicos anteponiendo guiones bajos a sus nombres, lo que ahora son ._x y ._y. Luego, define los métodos getter y setter para ambos atributos. En los métodos de establecimiento, .set_x() y .set_y(), se inserta la lógica de validación definida en el método .validate().

Ahora, debes usar la clase como en el siguiente código:

>>> from point_v1 import Point

>>> point = Point(42, 21)

>>> point.get_x()
42
>>> point.get_y()
21

>>> point.set_x(0)
>>> point.get_x()
0
>>> point.set_y("7")
Traceback (most recent call last):
    ...
ValueError: coordinates must be numbers

Tu clase funciona de manera diferente después de la actualización. En lugar de acceder directamente a los atributos .x y .y, debe utilizar los métodos getter y setter. La lógica de validación funciona, lo cual es genial, pero has roto la API de tu clase. Sus usuarios no podrán hacer algo como lo siguiente:

>>> point.x
Traceback (most recent call last):
    ...
AttributeError: 'Point' object has no attribute 'x'

Los usuarios antiguos de su clase se sorprenderán de que su código no funcione después de actualizar a su nueva versión de Point. Entonces, ¿cómo se puede evitar este tipo de problemas? El enfoque Pythonic consiste en convertir atributos públicos en propiedades en lugar de utilizar métodos getter y setter.

Puede utilizar la función incorporada property() para realizar esta conversión. Así es como puedes mantener la API de tu clase Point sin cambios:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = self.validate(value)

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self._y = self.validate(value)

    def validate(self, value):
        if not isinstance(value, int | float):
            raise ValueError("coordinates must be numbers")
        return value

Punto ahora se ve un poco diferente. No tiene métodos getter y setter formales. En cambio, tiene algunos métodos decorados con @property. Sí, la función incorporada property() se utiliza principalmente como decorador.

Los métodos que decoras con @property son equivalentes a los métodos getter. Mientras tanto, los métodos que decoras con el nombre del getter más .setter() son equivalentes a los métodos setter. Lo bueno de las propiedades es que aún puedes usar los atributos como atributos normales:

>>> from point_v2 import Point

>>> point = Point(42, 21)

>>> point.x
42
>>> point.y
21

>>> point.x = 0
>>> point.x
0

>>> point.x = "7"
Traceback (most recent call last):
    ...
ValueError: coordinates must be numbers

Al convertir atributos regulares en propiedades, puede agregarles un comportamiento similar a una función sin perder la capacidad de usarlos como atributos regulares. Las propiedades le evitan introducir cambios importantes en la API pública de su código, para que no rompa el código de sus usuarios.

Creación de clases y métodos estáticos: classmethod() y staticmethod()

Las clases le permiten definir piezas de código reutilizables que encapsulan datos y comportamiento en una sola entidad. Por lo general, los datos se almacenan en atributos, que son variables definidas dentro de las clases. Cuando se trata de comportamientos, usarás métodos, que son funciones definidas en clases.

En Python, tienes tres tipos diferentes de métodos:

  1. Métodos de instancia, que toman el objeto actual como primer argumento.
  2. Métodos de clase, que toman la clase actual como primer argumento.
  3. Métodos estáticos, que no toman ni la instancia actual ni la clase actual como argumentos

Los métodos de instancia deben tomar la instancia actual como argumento. Por convención, este argumento se llama self en Python.

Para crear un método de clase, necesita decorar el método con el decorador @classmethod. De manera similar, para crear un método estático, debe decorar el método con el decorador @staticmethod. Ambos decoradores son parte de las funciones integradas de Python.

Un caso de uso común para los métodos de clase es proporcionar múltiples constructores para una clase. Para ilustrar cómo escribir un método de clase, digamos que desea una clase Point que pueda construir utilizando coordenadas cartesianas o polares. En esta situación, puede hacer algo como lo siguiente:

import math

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def from_polar(cls, distance, angle):
        return cls(
            x=distance * math.cos(math.radians(angle)),
            y=distance * math.sin(math.radians(angle)),
        )

En este ejemplo, el método .from_polar() es un método de clase. Toma la clase actual como primer argumento, que normalmente se llama cls por convención. El método devuelve una nueva instancia de la clase calculando las coordenadas cartesianas a partir de las coordenadas polares.

Así es como puedes utilizar este método en la práctica:

>>> from point import Point

>>> point = Point.from_polar(20, 15)

>>> point.y
5.176380902050415
>>> point.x
19.318516525781366

En este fragmento de código, crea una nueva instancia Point utilizando el método de clase .from_polar(). En el ejemplo, llama al método en la clase en lugar de en una instancia para indicar que se trata de un método de clase. También puedes llamar a un método de clase en una instancia de su clase contenedora.

El tercer tipo de método es el método estático. Un método estático no toma la instancia o clase actual como argumento. Estos métodos son como funciones regulares que decides incluir en una clase determinada por conveniencia. Funcionalmente, también podrían definirse como funciones regulares en un módulo.

Por ejemplo, considere la siguiente clase Formatter:

class Formatter:
    @staticmethod
    def as_currency(value):
        return f"${value:,.2f}"

    @staticmethod
    def as_percent(value):
        return f"{value:.2%}"

Esta clase define dos métodos estáticos. El primer método toma un valor numérico y lo formatea como valor de moneda. El segundo método toma un valor numérico y lo expresa como porcentaje. Podría haber definido estos métodos como funciones regulares a nivel de módulo. Sin embargo, los ha definido en una clase como una forma de agruparlos convenientemente según cómo se utilizarán.

Puede utilizar esta clase como en los siguientes ejemplos:

>>> from formatting import Formatter

>>> Formatter.as_currency(1000)
'$1,000.00'

>>> Formatter.as_percent(0.75)
'75.00%'

>>> formatter = Formatter()
>>> formatter.as_currency(1000)
'$1,000.00'

>>> formatter.as_percent(0.8)
'80.00%'

Puede utilizar métodos estáticos llamándolos a la clase o a una de sus instancias. En este ejemplo, la clase Formatter funciona como un espacio de nombres donde defines métodos relacionados para mayor comodidad. Sin embargo, puede obtener el mismo resultado definiendo los métodos como funciones a nivel de módulo.

Gestión de atributos: getattr(), setattr() y delattr()

A veces, es posible que necesites acceder, configurar o eliminar atributos de tus objetos en Python. En la mayoría de los casos, puede realizar estas operaciones directamente utilizando la notación de puntos, el operador de asignación y la instrucción del.

En otras situaciones, sólo conoces los nombres de los atributos en tiempo de ejecución, por lo que no puedes acceder a ellos con la sintaxis normal. En estos casos, puede utilizar las funciones integradas getattr(), setattr() y delattr(). Estas funciones tienen las siguientes firmas:

getattr(object, name)
getattr(object, name, default)

setattr(object, name, value)

delattr(object, name)

En todos los casos, el argumento object debe tomar una instancia de una clase existente. De manera similar, name debe ser el nombre de un atributo o método como una cadena.

En la segunda firma de getattr(), el argumento default es un valor opcional que obtendrá si el atributo deseado no existe en el objeto de destino.

En la firma de setattr(), el argumento value debe contener el nuevo valor que desea asignar a un argumento determinado.

Para ilustrar cómo funcionan estas funciones, considere la siguiente clase:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

Esta clase tiene dos atributos de instancia, .name y .age. A continuación se explica cómo puede acceder, configurar o eliminar los atributos utilizando sus nombres como cadenas:

>>> from person import Person

>>> jane = Person("Jane", 25)

>>> getattr(jane, "name")
'Jane'
>>> getattr(jane, "age")
25

En estos ejemplos, utiliza getattr() para recuperar los valores almacenados en .name y .age. El primer argumento de esta función es el objeto del que necesita recuperar un atributo. El segundo argumento es el nombre del atributo como una cadena.

Ahora diga que desea actualizar la edad de Jane. Puedes hacer esto usando la función setattr():

>>> setattr(jane, "age", 26)
>>> jane.age
26

Luego, utiliza la función setattr() para asignar un nuevo valor al atributo .age. Esta función toma tres argumentos: el objeto, el nombre del atributo y el nuevo valor.

Finalmente, puedes usar la función incorporada delattr() para eliminar un atributo de un objeto determinado:

>>> delattr(jane, "age")
>>> jane.age
Traceback (most recent call last):
    ...
AttributeError: 'Person' object has no attribute 'age'

La función delattr() toma el objeto como primer argumento y el nombre del atributo como segundo argumento. Después de llamar a la función, intentar acceder a .age generará una excepción AttributeError.

En la práctica, las funciones integradas getattr(), setattr() y delattr() resultan útiles cuando es necesario manipular atributos. usando sus nombres como cadenas. Por ejemplo, digamos que desea crear una clase FileProcessor para leer y escribir archivos CSV y JSON. En esta situación, puede tener clases dedicadas para procesar cada tipo de archivo:

import csv
import json

class CSVProcessor:
    def __init__(self, filename):
        self.filename = filename

    def read(self):
        with open(self.filename, encoding="utf-8", newline="") as file:
            return list(csv.DictReader(file))

    def write(self, data):
        with open(
            self.filename, mode="w", encoding="utf-8", newline=""
        ) as file:
            writer = csv.DictWriter(file, fieldnames=data[0].keys())
            writer.writeheader()
            writer.writerows(data)

class JSONProcessor:
    def __init__(self, filename):
        self.filename = filename

    def read(self):
        with open(self.filename, encoding="utf-8") as file:
            return json.load(file)

    def write(self, data):
        with open(self.filename, mode="w", encoding="utf-8") as file:
            json.dump(data, file, indent=2)

En este archivo processors.py, define dos clases que pueden procesar archivos CSV y JSON, respectivamente. Ambas clases tienen los métodos .read() y .write(). Estas clases se ven bien, pero ahora necesitas hacerlas utilizables desde tu clase FileProcessor.

Para escribir la clase FileProcessor, puedes utilizar una técnica llamada delegación, que consiste en evaluar el atributo o método de un objeto en el contexto de otro objeto. Así es como puedes hacer esto en Python:

# ...

class FileProcessor:
    def __init__(self, filename, processor):
        self.filename = filename
        self.processor = processor(filename)

    def __getattr__(self, attr):
        return getattr(self.processor, attr)

En esta clase, usted define el método especial .__getattr__(). Este método admite operaciones de acceso a atributos en clases de Python. En la definición del método, utiliza la función getattr() para acceder a atributos y métodos desde el objeto procesador proporcionado.

En la práctica, se utiliza la combinación del método .__getattr__() y la función getattr() para implementar la delegación. La clase FileProcessor delega el procesamiento de archivos a la clase de procesador concreta que usted pasa durante la creación de instancias.

Así es como puedes usar la clase FileProcessor en tu código:

>>> from processors import FileProcessor

>>> file_proc = FileProcessor("products.csv", CSVProcessor)
>>> file_proc.read()
[
    {'product': 'Laptop', 'price': '1200', 'sold_units': '30'},
    {'product': 'Phone', 'price': '700', 'sold_units': '50'},
    {'product': 'Tablet', 'price': '450', 'sold_units': '100'},
    {'product': 'Desktop', 'price': '1000', 'sold_units': '20'},
    {'product': 'Monitor', 'price': '300', 'sold_units': '50'}
]

En este código, crea una instancia FileProcessor para procesar un archivo CSV con CSVProcessor. Aunque la instancia no tiene un método .read(), puedes llamar al método debido a la técnica de delegación que se basa en la función getattr().

Comprobando atributos: hasattr()

Otra función integrada estrechamente relacionada con atributos y métodos es la función hasattr(). Esta función le permite verificar si un objeto determinado tiene un determinado atributo o método. La función tiene la siguiente firma:

hasattr(object, name)

En esta firma, el argumento object puede tomar cualquier objeto de Python, mientras que el argumento name debe contener el nombre de un atributo como una cadena. Esta función es un predicado que devuelve True si el objeto tiene un atributo con el nombre proporcionado y False en caso contrario.

En la práctica, puede utilizar esta función para comprobar si un objeto tiene un atributo o método determinado antes de intentar utilizarlo. Por ejemplo, digamos que tiene las siguientes clases:

class Duck:
    def fly(self):
        print("The duck is flying")

    def swim(self):
        print("The duck is swimming")

class Pigeon:
    def fly(self):
        print("The pigeon is flying")

Estas clases representan dos aves diferentes. Ambas aves son capaces de volar, pero sólo el pato es capaz de nadar. Ahora digamos que desea usarlos en un bucle como el siguiente:

>>> from birds import Duck, Pigeon

>>> birds = [Duck(), Pigeon()]

>>> for bird in birds:
...     bird.fly()
...     bird.swim()
...
The duck is flying
The duck is swimming
The pigeon is flying
Traceback (most recent call last):
    ...
AttributeError: 'Pigeon' object has no attribute 'swim'

Este bucle funciona para la instancia de Duck. Sin embargo, genera una excepción AttributeError cuando llamas a .swim() en una instancia de Pigeon porque la clase no tiene este método. Para evitar este error, puede utilizar la función hasattr() para comprobar si el método existe antes de llamarlo:

>>> for bird in birds:
...     bird.fly()
...     if hasattr(bird, "swim"):
...         bird.swim()
...
The duck is flying
The duck is swimming
The pigeon is flying

Su código no falla ahora porque ha utilizado la función hasattr() para asegurarse de que el pájaro actual tenga el método .swim() antes de llamarlo.

Creación y verificación de tipos: type(), isinstance() y issubclass()

Python es un lenguaje de tipado dinámico, lo que significa que Python verifica los tipos solo mientras se ejecuta el código, y el tipo de una variable puede cambiar a lo largo de su vida. Debido a esta característica del lenguaje, es posible que necesites verificar explícitamente el tipo de un objeto antes de usarlo para que tu código no falle.

Para conocer el tipo de un objeto determinado, puede utilizar la función incorporada type():

>>> type(42)
<class 'int'>

>>> type(2.75)
<class 'float'>

>>> type("Hello")
<class 'str'>

Cuando llamas a type() con cualquier clase de Python como argumento, obtienes el tipo del objeto, al que también puedes llamar la clase del objeto. En este ejemplo, llamas a type() con un número entero como argumento y obtienes la clase int como respuesta. Luego, usas type() con un número de punto flotante y obtienes la clase float, y así sucesivamente.

Si desea verificar el tipo de un objeto con type(), puede hacer algo como lo siguiente:

>>> type(42) == int
True
>>> type(42) == float
False

Esta forma de usar type() funciona. Sin embargo, no es el enfoque recomendado. Aprenderá más sobre la verificación de tipos en un momento. Por ahora, seguirás aprendiendo los conceptos básicos de type(). Para comenzar, aquí están las firmas de la función:

type(object)
type(name, bases, dict, **kwds)

Ya has utilizado la primera firma. En esta firma, el argumento object representa cualquier objeto de Python.

La segunda firma es un poco más complicada. Utilizará esta firma para crear nuevas clases dinámicamente en lugar de determinar el tipo de objeto. Aquí hay un resumen de los argumentos y su significado:

name

El nombre de la clase.

base

Una tupla que contiene las clases base.

dict

Un diccionario de atributos y métodos definidos en el cuerpo de la clase.

**kwds

Argumentos de palabras clave adicionales que se pasan al constructor de metaclase

Cuando usas type() con estos argumentos, puedes crear clases dinámicamente. De esta manera, type() es una forma dinámica de la declaración class. Considere el siguiente ejemplo de juguete:

>>> def greet(self):
...     print("Hello, World!")
...

>>> DemoClass = type("DemoClass", (), {"value": 42, "greet": greet})
>>> DemoClass.value
42
>>> instance = DemoClass()
>>> instance.value
42
>>> instance.greet()
Hello, World!

>>> dir(instance)
[
    '__class__',
    '__delattr__',
    '__dict__',
    ...
    'greet',
    'value'
]

En este ejemplo rápido, utiliza type() para crear una clase de demostración que hereda automáticamente de object porque la tupla base está vacía. La nueva clase tendrá un método llamado .greet(), que habrás definido de antemano. También tiene un atributo de clase llamado .value que estableces en 42.

Para los atributos, debe proporcionar el nombre del atributo como una cadena y el valor del atributo. Para los métodos, debe proporcionar el nombre del método como una cadena y un objeto de método, que es un método sin paréntesis de llamada. Tenga en cuenta que los métodos de instancia como .greet() deben tomar el objeto actual como argumento, al que normalmente llama self.

Para ver un ejemplo más realista, supongamos que desea escribir una función que le permita crear clases dinámicamente a partir de diferentes esquemas de datos. En esta situación, puede hacer algo como lo siguiente:

def create_class(name, custom_members):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __repr__(self):
        return f"{name}({self.__dict__})"

    class_members = {
        "__init__": __init__,
        "__repr__": __repr__,
    }
    class_members.update(custom_members)

    return type(name, (), class_members)

En este código, creas una función que toma dos argumentos. El primer argumento, name, debe ser una cadena que proporcione un nombre de clase válido. El segundo argumento, custom_members, debería ser un diccionario de atributos y métodos.

Luego, define una función interna llamada .__init__(), que usará como inicializador de clase. La función .__repr__() le permitirá proporcionar una representación de cadena para los objetos de su clase.

A continuación, crea un diccionario para incluir las funciones como métodos para su clase y actualiza el diccionario con el contenido de class_members, que debe provenir del usuario.

Finalmente, utiliza la función type() para generar la clase con el nombre proporcionado y el diccionario de miembros. A continuación se muestran un par de ejemplos de cómo utilizar esta función:

>>> from factory import create_class

>>> User = create_class("User", {"name": "", "age": 0, "email": ""})
>>> Product = create_class(
...     "Product", {"name": "", "price": 0.0, "units": 0}
... )

>>> john = User(name="John", age=30, email="john@example.com")
>>> table = Product(name="Table", price=200.0, units=5)

>>> john.name
'John'
>>> john.age
30
>>> john.email
'john@example.com'

>>> table.name
'Table'
>>> table.price
200.0
>>> table.units
5

En este fragmento de código, primero crea dos clases usando create_class(). La primera clase representa a los usuarios y la segunda representa los productos. Ambos tienen diferentes conjuntos de atributos de instancia.

Luego, crea instancias concretas de cada clase con valores adecuados para los atributos. Finalmente, accede a los atributos utilizando la notación de puntos. ¡Genial! Tus clases funcionan como se esperaba.

La función type() es una gran herramienta para crear clases dinámicamente. Aunque también puede utilizar esta función para comprobar el tipo de un objeto, la herramienta recomendada para la comprobación explícita de tipos es la función incorporada isinstance() porque tiene en cuenta las subclases.

La firma para isinstance() es como la siguiente:

isinstance(object, classinfo)

En esta firma, object representa cualquier objeto de Python que le interese. El argumento classinfo es la clase o clases con las que desea comparar. Este argumento puede ser un objeto de clase única, una tupla de objetos de clase o un tipo de unión.

Considere los siguientes ejemplos en los que utiliza isinstance() para comprobar valores numéricos:

>>> isinstance(42, int)
True

>>> isinstance(42.0, (int, float))
True

>>> isinstance(42.0, int | float)
True

En el primer ejemplo, utiliza isinstance() para comprobar si 42 es una instancia de la clase int. En el segundo ejemplo, utiliza isinstance() para comprobar si 42.0 es una instancia de int o float. . En este ejemplo, utiliza una tupla de clases para proporcionar el argumento classinfo.

Finalmente, en el tercer ejemplo, realiza la misma verificación que en el segundo ejemplo. Esta vez, utiliza el carácter de barra vertical (|) para crear un tipo de unión con int y float. Tenga en cuenta que isinstance() es una función predicada que devuelve True si el objeto de entrada es una instancia de una de las clases proporcionadas.

La función isinstance() también considera subclases. Por ejemplo, la clase bool es una subclase de int, por lo que si comparas una instancia de bool con int, entonces obtendrás True como resultado:

>>> isinstance(False, int)
True

>>> type(False) == int
False

Debido a que bool es una subclase de int, la función isinstance() devuelve True cuando compara un valor booleano con la clase int. Tenga en cuenta que si intenta hacer una verificación similar con type(), obtendrá False porque type() no considera subclases.

Hay otra función incorporada que puede resultar útil para la verificación de tipos. La función se llama issubclass() y verifica si una clase determinada es una subclase de otra clase:

>>> issubclass(int, object)
True

>>> issubclass(bool, int)
True

>>> issubclass(int, float)
False

En el primer ejemplo, verifica si la clase int es una subclase de object. En este caso, obtienes True porque todas las clases de Python derivan de object. Luego, verificas si bool es una subclase de int, lo cual también es cierto, como ya aprendiste.

En el ejemplo final, usas issubclass() para comprobar si int es una subclase de float, que es False >.

La firma de issubclass() es la siguiente:

issubclass(class, classinfo)

En este caso, el argumento class es la clase que desea verificar, mientras que el argumento classinfo funciona igual que en isinstance() .

Comprobando objetos invocables: callable()

Un invocable en Python es cualquier objeto al que puedas llamar usando un par de paréntesis y una serie de argumentos si es necesario. En Python, los objetos invocables incluyen funciones, clases, métodos, instancias de clases con un método .__call__(), cierres y funciones generadoras.

A veces, es posible que necesites saber si un objeto es invocable antes de llamarlo en tu código. Para hacer esto, puede usar la función incorporada callable(), que toma un objeto como argumento y devuelve True si el objeto parece ser invocable. De lo contrario, devuelve False.

A continuación se muestran algunos ejemplos del uso de callable() con algunos objetos integrados:

>>> callable(abs)
True
>>> callable(int)
True
>>> callable(list)
True

>>> callable(True)
False
>>> callable(None)
False

En los primeros tres ejemplos, los argumentos de callable() son todos funciones, por lo que obtienes True como resultado. En los dos últimos ejemplos, utiliza los objetos True y None como argumentos. Estos objetos no son invocables, por lo que obtienes False como resultado.

Como ejemplo práctico, digamos que necesita crear una aplicación que procese comandos. Cada comando debe poder invocarse; de lo contrario, no será válido. Para comprobar esta condición, puede utilizar callable(). Aquí hay una implementación de juguete:

class CommandProcessor:
    def __init__(self):
        self.commands = {}

    def register_command(self, command):
        if not callable(command):
            raise ValueError("command is not callable")
        self.commands[command.__name__] = command

    def execute_command(self, name, *args, **kwargs):
        if (command := self.commands.get(name)) is None:
            raise ValueError(f"command '{name}' not found")
        return command(*args, **kwargs)

En esta clase, el método .register_command() usa callable() para verificar si el comando de entrada es un objeto invocable. Si ese es el caso, entonces registra el comando como válido. A continuación, tiene el método .execute_command() que ejecuta el comando como invocable.

A continuación se muestra un ejemplo de cómo utilizar esta clase:

>>> from commands import CommandProcessor

>>> command_processor = CommandProcessor()

>>> def add(a, b):
...     return a + b
...

>>> command_processor.register_command(add)
>>> command_processor.execute_command("add", 1, 2)
3

>>> subtract = 3 - 2
>>> command_processor.register_command(subtract)
Traceback (most recent call last):
    ...
ValueError: command is not callable

En este ejemplo, creará una instancia CommandProcessor para procesar comandos. Luego, escribes add() para usarlo como comando. Debido a que add() es un objeto invocable, puede registrarlo como un comando válido y ejecutarlo con el método .execute_command().

Finalmente, define la variable resta para contener el resultado de una operación de resta. Esta variable no se puede llamar. Por lo tanto, obtienes una excepción ValueError al registrarlo como un comando.

Accediendo a los miembros de los padres: super()

Cuando trabajas con herencia en clases de Python, a menudo necesitarás acceder a los atributos o métodos de una clase principal en una subclase. La forma Pythonic de hacer esto es utilizar la función incorporada super().

Un caso de uso común para super() es cuando necesita crear una subclase de una clase existente y necesita una forma adecuada de inicializar los atributos de la clase principal. Considere las siguientes clases que representan un rectángulo y un cuadrado:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * (self.length + self.width)

class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)

En este código, define una clase Rectangle con dos atributos, .length y .width. También tiene dos métodos para calcular el área y el perímetro del rectángulo. A continuación, define la clase Square. Dado que un cuadrado es un tipo de rectángulo con lados iguales, tiene sentido crear Square como una subclase de Rectangle y reutilizar la funcionalidad que ya implementó.

En el constructor Square(), solo necesita un único argumento para representar la longitud del lado. Puede usar este argumento para inicializar la clase principal usando super() como lo hizo en la línea resaltada. La función super() le da acceso a la clase principal Rectangle. Una vez que tenga acceso a la clase, puede llamar a su método .__init__() para inicializar los atributos .length y .width con el valor. del lado.

Construyendo objetos genéricos: object()

En Python, cada clase hereda implícitamente de la clase object, que está integrada en el lenguaje. En otras palabras, la clase object es la clase base para cada clase en Python:

>>> issubclass(int, object)
True
>>> issubclass(float, object)
True
>>> issubclass(bool, object)
True
>>> issubclass(dict, object)
True

>>> class DemoClass:
...     pass
...
>>> issubclass(DemoClass, object)
True

No importa si la clase que estás considerando es una clase integrada o personalizada, hereda de object.

En algunas situaciones, es posible que desee crear instancias de la clase object. Para hacer esto, puede usar la función incorporada object(), que en realidad es un constructor de clases en lugar de una función, pero la documentación de Python la incluye entre sus funciones incorporadas.

La función object() no toma ningún argumento y devuelve un nuevo objeto sin características, que tiene los métodos que son comunes a todos los objetos de Python. A diferencia de los objetos normales, el objeto que se obtiene al llamar a object() no tiene un atributo .__dict__, por lo que no puedes agregar atributos dinámicamente a este tipo de objeto. :

>>> obj = object()
>>> dir(obj)
[
    '__class__',
    '__delattr__',
    '__dir__',
    ...
    '__str__',
    '__subclasshook__'
]
>>> obj.attr = "Some value"
Traceback (most recent call last):
    ...
AttributeError: 'object' object has no attribute 'attr'

En este ejemplo, crea un nuevo objeto sin características llamando a object(). Con la función incorporada dir(), puede enumerar todos los métodos y atributos que proporciona este objeto. Finalmente, si intenta agregar un atributo a su objeto sin características de forma dinámica, obtendrá una excepción AttributeError.

En la práctica, puede utilizar la función object() cuando desee crear valores centinela únicos. Un valor centinela es un marcador único que puede utilizar para indicar la ausencia de un valor. También puede utilizarlo como condición para detener algoritmos iterativos o recursivos.

Para ilustrar cómo utilizar object() para crear un valor centinela, considere la siguiente clase Circle:

from time import sleep

SENTINEL = object()

class Circle:
    def __init__(self, radius):
        self.radius = radius
        self._diameter = SENTINEL

    @property
    def diameter(self):
        if self._diameter is SENTINEL:
            sleep(0.5)  # Simulate a costly computation
            self._diameter = self.radius * 2
        return self._diameter

En esta clase, tiene un atributo .radius que toma un valor en el momento de la creación de instancias. Luego, tiene un atributo no público llamado ._diameter que inicializa con la constante SENTINEL. Para crear esta constante, utiliza la función object().

Finalmente, tiene la propiedad .diameter, que calcula el diámetro a partir del radio proporcionado. En este ejemplo, utiliza sleep() del módulo time para simular que encontrar el diámetro es una operación costosa. Debido al costo de cálculo, decide almacenar en caché el diámetro para que se calcule una sola vez durante la vida del objeto.

Para comprobar si el diámetro ya se calculó, compare su valor actual con la constante SENTINEL. En este ejemplo, también podrías haber usado Ninguno como valor centinela porque es poco probable que el diámetro de un círculo tome un valor nulo. Sin embargo, cuando Ninguno puede ser un valor válido para el atributo en cuestión, entonces object() puede ser el camino a seguir.

Trabajar con ámbitos de Python

Python, como muchos otros lenguajes de programación, gestiona el concepto de ámbitos. El alcance rige cómo se buscan las variables y los nombres en su código. Determina la visibilidad de una variable o nombre dentro del código.

El alcance depende del lugar donde creas esa variable. Los alcances de Python siguen una regla conocida como regla LEGB. Las letras de este acrónimo significan ámbitos local, enclosing, global e integrado, y esta regla Resume los cuatro ámbitos que encontrarás en Python.

Encontrará dos funciones integradas que están estrechamente relacionadas con los ámbitos en Python. Estas funciones se enumeran en la siguiente tabla:

locals()

Actualiza y devuelve un diccionario que representa la tabla de símbolos local actual.

globals()

Devuelve un diccionario que representa la tabla de símbolos global actual.

En las siguientes secciones, aprenderá los conceptos básicos de estas funciones y cómo usarlas en su código Python para administrar algunos aspectos de sus alcances de nombres.

Inspeccionar y actualizar un ámbito local: locals()

El alcance local es el alcance de la función porque comprende el cuerpo de la función. Cada vez que llamas a una función, Python crea un nuevo alcance local para esa función. De forma predeterminada, los argumentos y nombres que asignas en el cuerpo de una función existen solo dentro del alcance local que Python crea cuando llamas a la función. Cuando la función regresa, el alcance local desaparece y los nombres se olvidan.

Si alguna vez necesita inspeccionar el estado de su alcance local actual, puede usar la función incorporada locals():

>>> def add(a, b):
...     result = a + b
...     print(locals())
...     return result
...

>>> add(2, 5)
{'a': 2, 'b': 5, 'result': 7}
7

En esta función, tomas dos argumentos, a y b. Estos argumentos son locales para add(), lo que significa que solo puedes acceder a ellos y usarlos dentro de la función. Luego, crea una variable local llamada result, que utiliza como variable temporal para almacenar los resultados del cálculo. La llamada a locals() devuelve un diccionario que contiene los nombres y valores de todas estas variables.

Tenga en cuenta que locals() toma la información del alcance solo hasta el punto en el que la llama:

>>> def add(a, b):
...     print(locals())
...     result = a + b
...     return result
...

>>> add(2, 5)
{'a': 2, 'b': 5}
7

En esta variación de add(), llamas a locals() al comienzo de la función. Entonces, solo obtienes los argumentos en el diccionario de resultados. Esto se debe a que cuando llamas a locals(), la variable result aún no se ha definido.

Inspeccionar y actualizar el alcance global: globals()

El alcance global es otro alcance importante en Python. Es el alcance a nivel de módulo y le permite definir variables o nombres globales. Puede acceder y modificar nombres globales desde cualquier lugar de su código.

Para inspeccionar y actualizar las variables y nombres que se encuentran en su alcance global actual, puede utilizar la función incorporada globals(). Por ejemplo, cuando inicia una nueva sesión REPL y llama a globals(), obtiene un resultado como el siguiente:

>>> globals()
{
    '__name__': '__main__',
    '__doc__': None,
    ...
    '__builtins__': <module 'builtins' (built-in)>
}

De forma predeterminada, cuando inicia una sesión REPL, el intérprete carga varios nombres y objetos en su ámbito global. Por ejemplo, el objeto __name__ contiene el nombre del módulo actual, que es "__main__" cuando estás en un módulo ejecutable. Si está en un módulo importado, esta variable contendrá el nombre del módulo.

Luego, tiene el nombre __doc__, que contendrá la cadena de documentación del módulo, si se proporciona. También tendrás varios otros nombres. Finalmente, tiene el nombre __builtins__, que contiene el espacio de nombres donde se definen los nombres integrados. Este es un módulo especial que incluye todas las funciones integradas cubiertas en este tutorial y varios otros objetos integrados, como excepciones.

Si comienza a definir variables y funciones en su sesión REPL, estos nombres se agregarán al diccionario que devuelve globals():

>>> language = "Python"
>>> number = 42
>>> def greet():
...     print("Hello, World!")
...
>>> class Demo:
...     pass
...

>>> globals()
{
    ...
    '__builtins__': <module 'builtins' (built-in)>,
    'language': 'Python',
    'number': 42,
    'greet': <function greet at 0x100984040>,
    'Demo': <class '__main__.Demo'>
}

En este ejemplo, define dos variables, una función y una clase. Cuando llamas a globals(), obtienes los nombres de todos esos objetos al final del diccionario resultante.

El diccionario que devuelve globals() es un diccionario grabable. Puede aprovechar esta función cuando necesite modificar o actualizar el contenido del alcance global manualmente. Un caso de uso común para esta función es cuando necesita cargar parámetros de configuración desde un archivo.

Por ejemplo, digamos que tiene el siguiente archivo JSON con algunos valores de configuración para su conexión de base de datos:

{
    "DATABASE_URL": "postgres://user:pass@localhost/dbname",
    "DEBUG_MODE": true,
    "MAX_CONNECTIONS": 10
}

Debe escribir una función que cargue este archivo y agregue los parámetros de configuración proporcionados a su alcance global actual. Aquí hay una posible implementación de esta función:

import json

def load_config(config_file):
    with open(config_file) as file:
        config = json.load(file)

    globals().update(config)

En esta función, primero abre el archivo de configuración y carga su contenido en un diccionario llamado config. Luego, actualiza el diccionario que globals() devuelve con el contenido de config.

Así es como funciona la función anterior:

>>> from config import load_config

>>> load_config("config.json")
>>> globals()
{
    ...
    'DATABASE_URL': 'postgres://user:pass@localhost/dbname',
    'DEBUG_MODE': True,
    'MAX_CONNECTIONS': 10
}

>>> MAX_CONNECTIONS
10

Después de llamar a load_config() con el archivo config.json como argumento, obtendrás los parámetros de configuración cargados como constantes en tu alcance global. Ahora puedes usar estas constantes directamente en tu código.

Introspección de objetos

En programación, la introspección de tipos es la capacidad de un programa de inspeccionar el tipo y las propiedades de un objeto en tiempo de ejecución. Todo en Python es un objeto, por lo que poder examinar tipos y propiedades en tiempo de ejecución es un activo valioso.

Aquí hay algunas funciones integradas que le permiten realizar algún tipo de introspección de tipos en Python:

id()

Devuelve la identidad de un objeto.

dir()

Devuelve una lista de nombres en el ámbito local actual o una lista de atributos de objeto

vars()

Devuelve el atributo __dict__ para un módulo, clase u objeto.

En las siguientes secciones, aprenderá cómo funcionan estas funciones y cómo puede usarlas en el código para realizar una introspección de tipos. Para comenzar, comenzará con la función id().

Conocer la identidad de un objeto: id()

En Python, cada objeto individual tiene una identidad asociada. Esta identidad es un número entero único y constante que identifica el objeto durante su vida. Dos objetos con tiempos de vida que no se superponen pueden tener la misma identidad.

Si alguna vez necesita conocer la identidad de un objeto determinado, puede utilizar la función id() con el objeto como argumento:

>>> id(42)
4315605776

>>> id("Python")
4315120464

>>> def greet():
...     print("Hello, World!")
...
>>> id(greet)
4307259040

>>> class Demo:
...     pass
...
>>> id(Demo)
4892672720

Cuando llamas a id() con cualquier objeto Python como argumento, obtienes un número que es la identidad del objeto. En la implementación CPython de Python, la identidad de un objeto es también la dirección de memoria donde vive ese objeto.

Conocer la identidad de un objeto puede ser de gran ayuda al depurar su código. Por ejemplo, digamos que desea escribir código que calcule valores individuales a partir de una especie de secuencia de Fibonacci. Puedes hacer esto de muchas maneras. Sin embargo, piensa en usar una clase con instancias invocables y un atributo de instancia que le permita almacenar en caché los valores ya calculados.

Aquí hay una posible implementación de esta clase:

class Fibonaccish:
    def __init__(self, initial_value=1):
        self._cache = [0, initial_value]

    def __call__(self, index):
        if index < len(self._cache):
            fib_number = self._cache[index]
            print(f"{index} {fib_number} id = {id(fib_number)}")
        else:
            fib_number = self(index - 1) + self(index - 2)
            self._cache.append(fib_number)
        return fib_number

En el método inicializador de Fibonaccish, usted define una lista para contener el caché de los valores calculados. Luego, define el método especial .__call__(), que permite que las instancias de su clase sean invocables como funciones.

En este método, usted determina si el valor de Fibonacci para el índice objetivo ya está calculado y almacenado en la memoria caché. Luego, agrega una llamada a print() que lo ayudará a depurar su código usando id() para garantizar que se utilicen los valores almacenados en caché.

Así es como funciona esta clase en la práctica:

>>> from fibonacci import Fibonaccish

>>> fibonacci_333 = Fibonacci(333)

>>> fibonacci_333(2)
0 0 id = 94800819952840
1 333 id = 140276932935312
333
>>> fibonacci_333(4)
2 333 id = 140276932934960
1 333 id = 140276932935312
2 333 id = 140276932934960
999

En este ejemplo de código, crea una instancia de Fibonaccish con un valor inicial de 333. Los primeros valores de esta secuencia serán 0, 333, 333, 666, 999 y 1665.

La instancia que crea es invocable, por lo que puede usarla como una función normal. Luego, llamas a la instancia con 2 como argumento. La llamada imprime la identidad de los valores 0 y 333 en el índice 0 y 1, respectivamente. A continuación, llamas a fibonacci_333() con 4 como argumento. En este caso, obtienes la identidad de 333 tres veces, tanto en el índice 1 como en el 2.

Cuando observas las identidades, te das cuenta de que tu función usa el mismo objeto para los mismos índices, mientras que es diferente para las dos instancias diferentes de 333. De esta manera, puede confirmar que la función utiliza el caché como se esperaba.

Si repites el ejemplo con la secuencia habitual de Fibonacci, Fibonacci(1), verás resultados ligeramente diferentes. En este caso, Python internará 1 bajo el capó, de modo que el mismo objeto se use tanto en el índice 1 como en el 2 de su caché.

Comprobando nombres y atributos: dir() y vars()

A veces, es necesario conocer los atributos o métodos definidos en un objeto o ámbito determinado. Para este tipo de requisitos, Python tiene dos funciones integradas diferentes, dir() y vars().

La función dir() sin argumentos devuelve la lista de nombres en el ámbito actual. Entonces, el resultado dependerá del lugar en el que llames a la función. Con cualquier objeto Python como argumento, dir() intenta devolver la lista de atributos de ese objeto.

Por ejemplo, si llamas a dir() sin argumentos en una nueva sesión REPL, obtendrás un resultado como el siguiente:

>>> dir()
[
    '__annotations__',
    '__builtins__',
    '__doc__',
    '__loader__',
    '__name__',
    '__package__',
    '__spec__'
]

Como puede ver, dir() devuelve la lista de nombres definidos en su alcance actual, que es el alcance global en este ejemplo.

Si llamas a dir() con un objeto Python como argumento, obtendrás una lista de los atributos y métodos del objeto. Si el objeto de entrada es una clase, obtendrá una lista de métodos y atributos de clase. Si el objeto es una instancia de una clase existente, obtendrá una lista de métodos, atributos de clase y atributos de instancia.

Considere el siguiente ejemplo que reutiliza su clase Rectangle de la sección sobre la función super():

>>> class Rectangle:
...     def __init__(self, length, width):
...         self.length = length
...         self.width = width
...     def area(self):
...         return self.length * self.width
...     def perimeter(self):
...         return 2 * (self.length + self.width)
...

>>> dir(Rectangle)
[
    '__class__',
    '__delattr__',
    '__dict__',
    ...
    'area',
    'perimeter'
]

>>> rectangle = Rectangle(2, 4)
>>> dir(rectangle)
[
    '__class__',
    '__delattr__',
    '__dict__',
    ...
    'area',
    'length',
    'perimeter',
    'width'
]

En la primera llamada a dir(), obtienes los atributos y métodos de la clase Rectangle. En este caso, utiliza el objeto de clase como argumento. En la segunda llamada a dir(), utiliza una instancia de Rectangle como argumento y obtiene todos los métodos, atributos de clase y atributos de instancia.

El comportamiento predeterminado de dir() es diferente para diferentes tipos de objetos. Aquí hay un resumen de estas diferencias:

A module object

Devuelve la lista de nombres definidos en el módulo.

A type or class object

Devuelve la lista de nombres de atributos y métodos de clase y de las clases base.

Other objects

Devuelve la lista de atributos y métodos, incluidos los atributos de clase y los atributos de las clases base.

También puede personalizar el comportamiento predeterminado de dir() proporcionando el método especial .__dir__() en sus clases personalizadas. Sin embargo, este tema está más allá del alcance de este tutorial.

La función vars() devuelve el atributo .__dict__ para un módulo, clase, instancia o cualquier otro objeto con un atributo .__dict__:

>>> vars(Rectangle)
mappingproxy(
{
    '__module__': '__main__',
    '__init__': <function Rectangle.__init__ at 0x10352d080>,
    'area': <function Rectangle.area at 0x10352d120>,
    'perimeter': <function Rectangle.perimeter at 0x10352d1c0>,
    '__dict__': <attribute '__dict__' of 'Rectangle' objects>,
    '__weakref__': <attribute '__weakref__' of 'Rectangle' objects>,
    '__doc__': None
})

>>> vars(rectangle)
{'length': 2, 'width': 4}

En este ejemplo, llamas a vars() con la clase Rectangle como argumento. Obtienes el atributo .__dict__ de la clase, que contiene métodos y atributos de clase. Tenga en cuenta que también contiene un atributo .__dict__ que contiene los atributos de las instancias de la clase. Ese .__dict__ es lo que obtienes cuando llamas a vars() con una instancia de la clase.

El atributo .__dict__ es un diccionario que funciona como un espacio de nombres que asigna nombres a objetos. Por ejemplo, puede asignar un nombre de método a un objeto de método o un nombre de atributo a un valor u objeto específico.

Ejecutar código Python desde cadenas

En situaciones excepcionales, puede resultar útil evaluar expresiones o ejecutar código que viene como un objeto de cadena. Esta práctica no es común en el código del mundo real porque puede no ser seguro, especialmente cuando el código de destino proviene de una fuente que no es confiable, como la entrada del usuario.

Independientemente de los problemas de seguridad involucrados, Python tiene tres funciones integradas que le permiten evaluar expresiones o ejecutar código que viene como una cadena. Aquí hay un resumen de estas funciones:

eval()

Evalúa expresiones arbitrarias de Python a partir de una cadena o entrada de código compilado.

exec()

Ejecuta código Python arbitrario a partir de una cadena o entrada de código compilado.

compile()

Genera un objeto de código compilado a partir de una cadena.

En las siguientes secciones, aprenderá los conceptos básicos de estas funciones. Para comenzar, comenzará usando la función eval() para evaluar expresiones de Python.

Ejecutando expresiones a partir de cadenas: eval()

En Python, una expresión es una combinación de objetos y operadores que devuelve un valor. Encontrará varios tipos de expresiones, incluidas expresiones matemáticas, booleanas, de comparación, expresiones bit a bit y más. Cuando trabaje con expresiones, puede ejecutarlas como código Python normal. Sin embargo, ¿qué sucede si necesita evaluar expresiones definidas como cadenas?

Por ejemplo, piense en cómo evaluaría lo siguiente:

"sum([2, 3, 4, 5]) / 4 + 100"

Si desea evaluar esta cadena como una expresión, tendrá que analizar la cadena y descubrir cómo extraer los operandos y operadores. Luego, puedes reconstruir la expresión y ejecutarla en el intérprete de Python. Este proceso puede parecer algo rápido. Sin embargo, puede resultar abrumador en la práctica, especialmente si se considera el número infinito de expresiones diferentes que es posible que deba evaluar en código real.

Afortunadamente, Python tiene una función eval() incorporada que te ayuda a evaluar expresiones que vienen como cadenas.

Si tiene una cadena que contiene una expresión Python válida, puede llamar a eval() con esa cadena como argumento. La función analizará la cadena, la compilará en código de bytes y finalmente la evaluará como una expresión normal:

>>> eval("sum([2, 3, 4, 5]) / 4 + 100")
103.5

¡Guau! ¡Eso fue rápido y sencillo! Simplemente pasó su cadena a eval(), ejecutó el código y obtuvo el resultado de la expresión.

La firma de eval() se parece a la siguiente:

eval(expression[, globals[, locals]])

El primer argumento, expresión, contiene la expresión que necesita evaluar. El resto de argumentos son opcionales. Por eso están entre corchetes. He aquí un resumen de estos argumentos y su significado:

expression

Una cadena que contiene una expresión de Python válida

globals

Un diccionario que contiene un espacio de nombres global para usar en la llamada a eval()

locals

Un diccionario que contiene un espacio de nombres local para usar en la llamada a eval()

Ya has visto un ejemplo del uso del argumento expresión, por lo que ahora puedes centrarte en los otros dos argumentos. En cada ejemplo, deberá proporcionar una expresión.

A continuación se muestra un ejemplo del uso del argumento globals:

>>> numbers = [2, 3, 4, 5]
>>> n = len(numbers)

>>> eval("sum(numbers) / n + 100")
103.5

>>> eval("sum(numbers) / n + 100", {})
Traceback (most recent call last):
    ...
NameError: name 'numbers' is not defined

>>> eval("sum(numbers) / n + 100", {"numbers": numbers, "n": n})
103.5

De forma predeterminada, eval() tiene acceso al alcance global, por lo que puede usar todos los nombres definidos en este alcance en la expresión que pasa a la función. Si configura globals en un diccionario vacío, restringe el acceso al alcance global y la función falla.

Finalmente, puede usar un diccionario explícito, como lo hizo en el ejemplo final, para proporcionar las variables globales que desea usar al evaluar la expresión de destino.

El argumento locals funciona de manera similar. Se necesita un diccionario de nombres locales:

>>> def evaluator(expression):
...     numbers = [2, 3, 4, 5]
...     n = len(numbers)
...     return eval(expression, {}, {"numbers": numbers, "n": n})
...

>>> evaluator("sum(numbers) / n + 100")
103.5

Dentro de la función evaluator(), define numbers y n como variables locales. En la llamada a eval(), utiliza un diccionario vacío para globals y un diccionario que contiene las variables locales para locals.

Aunque la función eval() puede parecer una herramienta increíble, debes tener cuidado al usarla en tu código. En la práctica, estará más seguro si no utiliza esta herramienta en código del mundo real. ¿Por qué?

La función eval() tiene implicaciones de seguridad que pueden ser difíciles de eludir. Por ejemplo, si utiliza la función para evaluar expresiones proporcionadas por usuarios externos, expone su sistema a la ejecución de código Python arbitrario.

Para obtener más información sobre cómo reducir los riesgos de seguridad asociados con eval(), consulte Minimización de los problemas de seguridad de eval() en Python . eval(): tutorial sobre evaluación dinámica de expresiones.

Ejecutar código desde cadenas: exec() y compile()

La función eval() es una poderosa herramienta en Python. Sin embargo, está diseñado para evaluar expresiones. A veces, es posible que desees ejecutar fragmentos de código más complejos que vienen como cadenas. Por ejemplo, es posible que desee ejecutar bucles, declaraciones condicionales, declaraciones compuestas e incluso scripts completos. En este escenario, puede utilizar las funciones integradas exec() y compile().

Aquí está la firma de la función exec():

exec(code [, globals [, locals]])

El argumento code puede ser una cadena que contenga código Python válido. También puede ser un objeto de código compilado, que puede crear con la función compile(). Aprenderá sobre compile() en un momento. Por ahora, utilizará una cadena para proporcionar el argumento code.

Si code viene como una cadena, entonces exec() lo analiza como una secuencia de declaraciones de Python. Luego, compila el código en código de bytes y, finalmente, ejecuta el código a menos que ocurra un error de sintaxis durante el paso de análisis o compilación.

Considere el siguiente ejemplo:

>>> functions = [
...     "def add(a, b): return a + b",
...     "def subtract(a, b): return a - b",
...     "def multiply(a, b): return a * b",
...     "def divide(a, b): return a / b",
... ]

>>> for function in functions:
...     exec(function)
...

En este ejemplo, define una lista de cadenas. Cada cadena contiene funciones de Python para una operación aritmética básica. Luego, inicia un bucle sobre la lista. Con exec(), ejecutas las cadenas que definen las funciones. Este paso lleva todas las funciones a su alcance global actual. Ahora puedes usarlos como lo harías con una función normal:

>>> add(1, 2)
3
>>> subtract(3, 2)
1
>>> multiply(2, 3)
6
>>> divide(6, 3)
2.0

Las funciones aritméticas ahora están disponibles en su alcance global, por lo que puede usarlas para ejecutar sus cálculos.

Al igual que eval(), exec() toma los argumentos globals y locals, que nuevamente son opcionales. Estos argumentos tienen significados similares en ambas funciones, por lo que puedes probarlos como ejercicio.

Cuando tenga una cadena que contenga código que reutilizará varias veces, puede usar la función compile() para compilar el código una vez y usarlo en todas partes. Esta práctica hará que su código sea más eficiente y rápido porque el paso de compilación se ejecuta solo una vez.

La firma de compile() se parece a la siguiente:

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)

Esta firma es un poco complicada porque tiene varios argumentos que debes comprender. Aquí hay un resumen de los argumentos y su significado:

source

Contiene el código que necesita compilar en código de bytes.

filename

Mantenga el archivo desde el cual se leyó el código.

mode

Especifica qué tipo de código debe compilarse.

flags and dont_inherit

Controla qué opciones del compilador deben activarse y qué funciones futuras deben permitirse.

optimize

Especifica el nivel de optimización de la compilación.

Para leer desde un objeto de cadena, deberá establecer filename en el valor "<string>". El argumento mode puede tomar uno de los siguientes valores:

  • "eval" cuando source consta de una sola expresión
  • "exec" cuando source es una secuencia de declaraciones
  • "single" cuando source es una declaración interactiva única

Dependiendo del código fuente y la función que planeas usar para ejecutarlo, seleccionarás el primer o el segundo valor. El argumento single resulta útil cuando desea ejecutar una declaración como print("Hello, World!") que normalmente ejecutaría en una sesión interactiva.

Para ilustrar cómo utilizar compile(), considere el siguiente ejemplo de juguete:

>>> code = """
... result = sum(number for number in iterable if not number % 2)
... """

>>> compiled_code = compile(code, "<string>", "exec")

>>> context = {"iterable": [1, 2, 3, 4]}
>>> exec(compiled_code, context)
>>> context["result"]
6

>>> context = {"iterable": [10, 40, 50, 20]}
>>> exec(compiled_code, context)
>>> context["result"]
120

En este ejemplo, tiene un fragmento de código en una cadena. El código consiste en una llamada a sum() que envuelve una expresión generadora que toma un iterable de números y devuelve los números pares. A continuación, utiliza la función compile() para compilar la cadena en un objeto de código listo para su ejecución. El diccionario context contiene un iterable de números.

Llamas a exec() con el código compilado y el diccionario de contexto como argumentos. Tenga en cuenta que utiliza context para proporcionar el argumento globals. La llamada a exec() actualizará este diccionario con cualquier nombre que defina en el código compilado. En este ejemplo específico, context termina manteniendo la variable result con la suma de los números pares en el iterable.

Para acceder al valor calculado, utilice el diccionario context con la clave "result". En el ejemplo final, reutiliza el código compilado para realizar un cálculo similar con una lista de valores diferente.

Uso de funciones diversas

Python tiene algunas otras funciones integradas que cubren diversos temas. Aquí hay un resumen de estas funciones:

help()

Invoca el sistema de ayuda integrado.

hash()

Calcula el valor hash de un objeto.

__import__()

Invocado por la declaración import

memoryview()

Devuelve un objeto de vista de memoria

En las siguientes secciones, aprenderá los conceptos básicos de estas funciones y cómo usarlas en su código Python o en una sesión interactiva del lenguaje. Para comenzar, comenzará con la función incorporada help().

Accediendo al sistema de ayuda integrado: help()

La función incorporada help() resulta útil cuando trabajas en una sesión REPL de Python. Esta función le brinda acceso al sistema de ayuda interactivo integrado. Continúe y abra una sesión interactiva de Python en su terminal. Luego, llame a help() sin argumentos. Se le presentará el sistema de ayuda:

>>> help()

Welcome to Python 3.x's help utility!

If this is your first time using Python, you should definitely check out
the tutorial on the internet at https://docs.python.org/3.x/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, symbols, or topics, type
"modules", "keywords", "symbols", or "topics".  Each module also comes
with a one-line summary of what it does; to list the modules whose name
or summary contain a given string such as "spam", type "modules spam".

help>

El resultado le da una cálida bienvenida a la utilidad de ayuda de Python. Luego, le sugiere que siga el tutorial oficial de Python si es nuevo en el idioma. En los párrafos tercero y cuarto, se le brindan instrucciones para utilizar el sistema de ayuda.

Al final de la página, tiene el mensaje help> esperando su entrada. Continúe y escriba el nombre str y luego presione Enter. Verás algo como lo siguiente:

Help on class str in module builtins:

class str(object)
 |  str(object='') -> str
 |  str(bytes_or_buffer[, encoding[, errors]]) -> str
 |
 |  Create a new string object from the given object. If encoding or
 |  errors is specified, then the object must expose a data buffer
 |  that will be decoded using the given encoding and error handler.
 |  Otherwise, returns the result of object.__str__() (if defined)
 |  or repr(object).
 |  encoding defaults to sys.getdefaultencoding().
 |  errors defaults to 'strict'.
 |
 |  Methods defined here:
 |
 |  __add__(self, value, /)
 |      Return self+value.
 |
 |  __contains__(self, key, /)
 |      Return key in self.
 ...

Esta es la página de ayuda para la clase str. En esta página, encontrará información detallada sobre la clase y sus objetivos. Para salir de la página y volver al sistema de ayuda, continúe y presione Q.

Mientras esté en el sistema de ayuda, puede consultar la ayuda de muchos objetos de Python, incluidos módulos integrados, funciones, palabras clave y mucho más. Adelante, pruébalo. ¡Quizás encuentres información útil!

Hay una segunda forma de utilizar la función help(). Primero, escriba Q en el indicador help> y luego presione Enter para regresar. a su sesión interactiva. Una vez allí, puedes llamar a help() con cualquier objeto Python como argumento. Por ejemplo, si llamas a la función con la clase str como argumento, te llevará a la misma página que viste antes:

>>> help(str)

Esta forma de llamar a help() le brinda acceso rápido a la página de ayuda de un objeto determinado. Es importante tener en cuenta que puede utilizar el objeto directamente como argumento para help() o el nombre del objeto como una cadena como en help("str") . Sin embargo, en la mayoría de los casos, es más seguro utilizar el nombre del objeto como una cadena:

>>> help(sys)
Traceback (most recent call last):
    ...
NameError: name 'sys' is not defined

En este ejemplo, intenta acceder a la página de ayuda del módulo sys. Utiliza el nombre del módulo como argumento y obtiene una excepción NameError porque el módulo no está presente en su alcance actual. La llamada a help() funcionará de forma segura si usa el nombre del módulo como una cadena, como en "sys". ¡Anímate y pruébalo!

Creando códigos hash: hash()

Si trabaja en campos como la integridad de los datos, la seguridad o la criptografía, es posible que esté familiarizado con los códigos hash. Un código hash es un número que puede actuar como una huella digital para un dato determinado. Suele ser mucho más pequeño que los datos originales y permite verificar su integridad.

Para crear un código hash para un objeto determinado, necesita una función hash. Python tiene su propia función incorporada para crear códigos hash. La función se llama convenientemente hash().

La firma de hash() es como la siguiente:

hash(object)

Toma un objeto como argumento y devuelve el valor hash del objeto de entrada. El valor hash debe ser un número entero.

A continuación se muestran algunos ejemplos del uso de la función hash():

>>> hash(42)
42
>>> hash(2.7)
1614090106449586178

>>> hash("Hello")
-6239611042439236057

>>> hash(int)
270201092

>>> class DemoClass: pass
...
>>> hash(DemoClass)
346520709

>>> demo_instance = DemoClass()
>>> hash(demo_instance)
271491289

En estos ejemplos, ha utilizado la función hash() con diferentes objetos, incluidos valores numéricos, cadenas, objetos de función y clases personalizadas. En todos los casos, obtienes un código hash único.

En la práctica, hay objetos que no tienen valor hash:

>>> hash([1, 2, 3])
Traceback (most recent call last):
    ...
TypeError: unhashable type: 'list'

>>> hash({"one": 1, "two": 2})
Traceback (most recent call last):
    ...
TypeError: unhashable type: 'dict'

>>> hash({"red", "green", "bleu"})
Traceback (most recent call last):
    ...
TypeError: unhashable type: 'set'

Notarás que los objetos mutables no se pueden usar mediante hash en Python porque puedes cambiar el valor de un objeto mutable durante su vida útil.

Importación de objetos desde nombres de cadenas: __import__()

La función __import__() incorporada de Python es una herramienta avanzada que no es común en la programación diaria. La función se llama internamente mediante la instrucción import. Se desaconseja el uso directo de __import__() en favor de importlib.import_module(). Sin embargo, es una función incorporada, por lo que aprenderá un poco sobre ella en esta sección.

La firma de __import__() se parece a la siguiente:

__import__(name, globals=None, locals=None, fromlist=(), level=0)

Con esta función, puede importar un módulo por su nombre. Este nombre debe ser una cadena. Aquí hay un resumen de los argumentos de la función y su significado:

name

El nombre de un módulo como una cadena

globals

Un diccionario que representa el espacio de nombres global.

locals

Un diccionario que representa el espacio de nombres local.

fromlist

Una lista de objetos o submódulos que deben importarse desde el módulo.

level

Un valor positivo que indica el número de directorios principales para buscar en relación con el directorio del módulo que llama a __import__()

Para hacer algo equivalente a import sys con la función __import__(), puedes hacer algo como lo siguiente:

>>> sys = __import__("sys")
>>> sys
<module 'sys' (built-in)>

En este ejemplo, crea una variable sys y le asigna el resultado de llamar a __import__() con la cadena "sys" como argumento. Esta llamada a __import__() importa el módulo sys a su alcance global actual.

Manipular datos binarios de manera eficiente: memoryview()

La función incorporada memoryview() le permite acceder a los datos internos de un objeto que admite el protocolo de búfer, como array.array, bytes objetos y bytearray. Puede utilizar esta función para manipular grandes conjuntos de datos o interactuar con datos binarios.

Por ejemplo, si tiene un conjunto de datos grande y desea utilizar una parte del mismo, crear una copia sería ineficiente. En su lugar, puede crear un objeto memoryview para acceder a los datos sin copiarlos. Esto le permite utilizar menos memoria y aumenta la velocidad de ejecución.

Por ejemplo, supongamos que tiene los datos de píxeles de una imagen representados como bytearray y desea invertir los valores de píxeles. Para realizar esta operación de manera eficiente, puede utilizar la función memoryview():

>>> image = bytearray([0, 127, 255, 64, 128, 192, 32, 96, 160])
>>> mv = memoryview(image)

>>> for i in range(len(mv)):
...     mv[i] = 255 - mv[i]
...

>>> list(mv)
[255, 128, 0, 191, 127, 63, 223, 159, 95]
>>> list(image)
[255, 128, 0, 191, 127, 63, 223, 159, 95]

En este ejemplo, crea un objeto memoryview para acceder a los datos que representan su imagen. En el bucle for, iteras sobre los datos e inviertes los valores de los píxeles. La transformación se refleja en los datos originales.

En resumen, la función memoryview() es una poderosa herramienta para trabajar con objetos que admiten el protocolo de búfer sin copiar los datos, lo que hace que su código sea más eficiente en memoria y más rápido.

Conclusión

Ha aprendido los conceptos básicos de las funciones integradas de Python. Estas son funciones que puedes usar directamente sin importar nada porque están disponibles en el ámbito o espacio de nombres integrado.

Las funciones integradas resuelven una amplia gama de problemas de programación comunes, como realizar operaciones matemáticas, trabajar con tipos de datos comunes, procesar iterables de datos, manejar entradas y salidas, trabajar con ámbitos y más.

En este tutorial, has aprendido:

  • Los conceptos básicos de las funciones integradas de Python
  • Acerca de los casos de uso comunes de las funciones integradas de Python
  • Cómo utilizar estas funciones para resolver problemas prácticos en Python

Con este conocimiento, tienes habilidades básicas de Python que te ayudarán a escribir código Pythonic sólido. Más importante aún, ahora conoce todas estas increíbles funciones y puede usarlas en su código para abordar tareas comunes de manera eficiente sin tener que reinventar la rueda.