Clase 2: Modularización y tipos compuestos


Nota

Escenas del capítulo anterior:

En la clase anterior preparamos la infraestructura:

  • Instalamos los programas y paquetes necesarios (conda).

  • Aprendimos como ejecutar: una consola usual, de ipython, o iniciar un jupyter notebook.

  • Aprendimos a utilizar la consola como una calculadora

  • Vimos algunos comandos mágicos y como enviar comandos al sistema operativo

  • Aprendimos como obtener ayuda

  • Aprendimos a crear ambientes virtuales.

  • Iniciamos los primeros pasos del lenguaje

Veamos un ejemplo completo de un programa (semi-trivial):

# Definición de los datos
r = 9.
pi = 3.14159
#
# Cálculos
A = pi*r**2
As = 4 * A
V = 4*A*r/3
#
# Salida de los resultados
print("Para un círculo de radio",r,"cm, el área es",A,"cm²")
print("Para una esfera de radio",r,"cm, el área es",As,"cm²")
print("Para una esfera de radio",r,"cm, el volumen es",V,"cm³")

En este ejemplo simple, definimos algunas variables con los datos del problema (r y pi), realizamos cálculos y sacamos por pantalla los resultados. A diferencia de otros lenguajes, python no necesita una estructura rígida, con definición de un programa principal.

Otro punto importante de este ejemplo es el uso de comentarios. El caracter “#” inicia un comentario de línea, y el intérprete de python ignora todos lo que viene a continuación hasta que encuentra una nueva línea.

Modularización y encapsulado

Una característica importante de la programación es la posibilidad de definir partes del código para realizar alguna tarea específica y poder reutilizarla. En Python, una de las maneras más efectivas de hacer esto es mediante la definición de funciones. Ya vimos un ejemplo, con la función print(), que se usa para imprimir valores a pantalla (por defecto) o a un archivo. Existen otras funciones ya definidas en Python (len(), max(), min(), abs(), etc). Puede ver la lista completa en la documentación.

Definición básica de funciones

Tomemos el ejemplo del programa anterior para definir una función que calcule el área de un disco

def area_disco(r):
  """Cálculo del área de un círculo de radio `r`"""
  # Definición de constantes
  pi = 3.141593
  # Cálculo
  A = pi * r**2
  return A
print("Terminó la función")
Terminó la función

Puntos a notar:

  • Las funciones se definen utilizando la palabra def seguida por el nombre.

  • A continuación, entre paréntesis se escriben los argumentos, en este caso el radio r.

  • La función devuelve algo; puede ser uno o más valores.

  • Lo que devuelve la función se especifica mediante la palabra reservada return (en este caso A que es un valor real). Si es más de un valor, se separan con comas. Si una función no devuelve algo explícitamente, entonces devuelve None.

  • Al principio de la definición de la función se escribe el string de documentación.

help(area_disco)
Help on function area_disco in module __main__:

area_disco(r)
    Cálculo del área de un círculo de radio r
R = 2
Area = area_disco(R)
print("Para un círculo de radio", R, "cm, el área es",Area,"cm²")
Para un círculo de radio 2 cm, el área es 12.566372 cm²
print("Para un círculo de radio", 3, "cm, el área es",area_disco(3.),"cm²")
Para un círculo de radio 3 cm, el área es 28.274337 cm²

Módulos

Los módulos son el mecanismo de Python para reusar código. Existen varios módulos que son parte de la biblioteca standard para distintos tipos de aplicaciones. Además existe un repositorio en la que contribuye la comunidad, con bibliotecas para muchos usos diversos.

El uso de módulos es muy simple, para poder aprovecharlo necesitaremos saber dos cosas:

  • Qué funciones están ya definidas y listas para usar

  • Cómo acceder a ellas

Empecemos con la segunda cuestión. Para utilizar las funciones debemos importarlas en la forma import modulo, donde modulo es el nombre que queremos importar.

Esto nos lleva a la primera cuestión: cómo saber ese nombre, y que funciones están disponibles. La respuesta es: la documentación.

Una vez importado, podemos utilizar constantes y funciones definidas en el módulo con la notación “de punto”: modulo.funcion().

Módulo math

El módulo math contiene las funciones más comunes (trigonométricas, exponenciales, logaritmos, etc) para operar sobre números de punto flotante, y algunas constantes importantes (pi, e, etc). En realidad es una interface a la biblioteca math en C.

import math
# algunas constantes y funciones elementales
raiz5pi = math.sqrt(5*math.pi)
print (raiz5pi, math.floor(raiz5pi), math.ceil(raiz5pi))
print (math.e, math.floor(math.e), math.ceil(math.e))
# otras funciones elementales
print (math.log(1024,2), math.log(27,3))
print (math.factorial(7), math.factorial(9), math.factorial(10))
print ('Combinatorio: C(6,2):',math.factorial(6)/(math.factorial(4)*math.factorial(2)))
3.963327297606011 3 4
2.718281828459045 2 3
10.0 3.0
5040 362880 3628800
Combinatorio: C(6,2): 15.0
math??
Type:        module
String form: <module 'math' from '/usr/lib64/python3.13/lib-dynload/math.cpython-313-x86_64-linux-gnu.so'>
File:        /usr/lib64/python3.13/lib-dynload/math.cpython-313-x86_64-linux-gnu.so
Docstring:
This module provides access to the mathematical functions
defined by the C standard.

A veces, sólo necesitamos unas pocas funciones de un módulo. Entonces para abreviar la notación combiene importar sólo lo que vamos a usar, usando la notación:

from xxx import yyy

from math import sqrt, pi, log
import math
raiz5pi = sqrt(5*pi)
print (log(1024, 2))
print (raiz5pi, math.floor(raiz5pi))
10.0
3.963327297606011 3
import math as m
m.sqrt(3.2)
1.7888543819998317
import math
print(math.sqrt(-1))
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

Cell In[11], line 2
      1 import math
----> 2 print(math.sqrt(-1))


ValueError: math domain error

Módulo cmath

El módulo math no está diseñado para trabajar con números complejos, para ello existe el módulo cmath

import cmath
print('Usando cmath (-1)^0.5=', cmath.sqrt(-1))
print(cmath.cos(cmath.pi/3 + 2j))
Usando cmath (-1)^0.5= 1j
(1.8810978455418161-3.1409532491755083j)

Si queremos calcular la fase (el ángulo que forma con el eje x) podemos usar la función phase

z = 1 + 0.5j
cmath.phase(z)                  # Resultado en radianes
0.4636476090008061
math.degrees(cmath.phase(z))    # Resultado en grados
26.56505117707799
help(cmath)
Help on module cmath:

NAME
    cmath

MODULE REFERENCE
    https://docs.python.org/3.13/library/cmath.html

    The following documentation is automatically generated from the Python
    source files.  It may be incomplete, incorrect or include features that
    are considered implementation detail and may vary between Python
    implementations.  When in doubt, consult the module reference at the
    location listed above.

DESCRIPTION
    This module provides access to mathematical functions for complex
    numbers.

FUNCTIONS
    acos(z, /)
        Return the arc cosine of z.

    acosh(z, /)
        Return the inverse hyperbolic cosine of z.

    asin(z, /)
        Return the arc sine of z.

    asinh(z, /)
        Return the inverse hyperbolic sine of z.

    atan(z, /)
        Return the arc tangent of z.

    atanh(z, /)
        Return the inverse hyperbolic tangent of z.

    cos(z, /)
        Return the cosine of z.

    cosh(z, /)
        Return the hyperbolic cosine of z.

    exp(z, /)
        Return the exponential value e**z.

    isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)
        Determine whether two complex numbers are close in value.

          rel_tol
            maximum difference for being considered "close", relative to the
            magnitude of the input values
          abs_tol
            maximum difference for being considered "close", regardless of the
            magnitude of the input values

        Return True if a is close in value to b, and False otherwise.

        For the values to be considered close, the difference between them must be
        smaller than at least one of the tolerances.

        -inf, inf and NaN behave similarly to the IEEE 754 Standard. That is, NaN is
        not close to anything, even itself. inf and -inf are only close to themselves.

    isfinite(z, /)
        Return True if both the real and imaginary parts of z are finite, else False.

    isinf(z, /)
        Checks if the real or imaginary part of z is infinite.

    isnan(z, /)
        Checks if the real or imaginary part of z not a number (NaN).

    log(z, base=<unrepresentable>, /)
        log(z[, base]) -> the logarithm of z to the given base.

        If the base is not specified, returns the natural logarithm (base e) of z.

    log10(z, /)
        Return the base-10 logarithm of z.

    phase(z, /)
        Return argument, also known as the phase angle, of a complex.

    polar(z, /)
        Convert a complex from rectangular coordinates to polar coordinates.

        r is the distance from 0 and phi the phase angle.

    rect(r, phi, /)
        Convert from polar coordinates to rectangular coordinates.

    sin(z, /)
        Return the sine of z.

    sinh(z, /)
        Return the hyperbolic sine of z.

    sqrt(z, /)
        Return the square root of z.

    tan(z, /)
        Return the tangent of z.

    tanh(z, /)
        Return the hyperbolic tangent of z.

DATA
    e = 2.718281828459045
    inf = inf
    infj = infj
    nan = nan
    nanj = nanj
    pi = 3.141592653589793
    tau = 6.283185307179586

FILE
    /usr/lib64/python3.13/lib-dynload/cmath.cpython-313-x86_64-linux-gnu.so

Ejercicios 02 (a)

  1. Escriba funciones que calculen:

  • El volumen de una esfera

  • El volumen de una caja de lados L1, L2, L3

  1. Escriba una función que tome como argumentos tres valores: a, b, c y calcule los ceros (raíces) del polinomio \(a x^{2}\, + b\, x + c\).

  2. Considere un polígono regular de \(N\) lados inscripto en un círculo de radio unidad:

    • Escriba una función que tome como argumento el número de lados \(N\) y calcule el ángulo interior del polígono regular correspondiente (por ejemplo el de un triángulo es 60 grados, de un cuadrado es 90 grados, y de un pentágono es 108 grados). Pruebe su función para valores de \(N= 3, 5, 6, 8, 9, 10, 12\).

    • ¿Puede calcular la longitud del lado de los polígonos regulares si se encuentran inscriptos en un círculo de radio unidad?

En todos los casos, pruebe las funciones escritas

.. image:: figuras/interior-angles-square.png
.. image:: figuras/interior-angles-pentagon.png

Tipos simples: Números

  • Números Enteros

  • Números Reales o de punto flotante

  • Números Complejos

Mencionamos anteriormente que todos las entidades en Python son objetos, que tienen al menos tres atributos: tipo, valor e identidad. Pero además, puede tener otros atributos como datos o métodos. Por ejemplo los números enteros, unos de los tipos más simples que usaremos, tienen métodos que pueden resultar útiles en algunos contextos.

a = 3                           # Número entero
print(type(a), a.bit_length(), sep="\n")
<class 'int'>
2
a.bit_length?
Signature: a.bit_length()
Docstring:
Number of bits necessary to represent self in binary.

>>> bin(37)
'0b100101'
>>> (37).bit_length()
6
Type:      builtin_function_or_method
b = 127
print(type(b))
print(b.bit_length())
<class 'int'>
7

En estos casos, usamos el método bit_length de los enteros, que nos dice cuántos bits son necesarios para representar un número. Para verlo utilicemos la función bin() que nos da la representación en binario de un número entero

# bin nos da la representación en binarios
print(a, "=", bin(a), "->", a.bit_length(),"bits")
print(b, "=", bin(b), "->", b.bit_length(),"bits")
3 = 0b11 -> 2 bits
127 = 0b1111111 -> 7 bits

Esto nos dice que el número 3 se puede representar con dos bits, y que para representar el número 127 se necesitan 7 bits.

help(a)
Help on int object:

class int(object)
 |  int([x]) -> integer
 |  int(x, base=10) -> integer
 |
 |  Convert a number or string to an integer, or return 0 if no arguments
 |  are given.  If x is a number, return x.__int__().  For floating-point
 |  numbers, this truncates towards zero.
 |
 |  If x is not a number or if base is given, then x must be a string,
 |  bytes, or bytearray instance representing an integer literal in the
 |  given base.  The literal can be preceded by '+' or '-' and be surrounded
 |  by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.
 |  Base 0 means to interpret the base from the string as an integer literal.
 |  >>> int('0b100', base=0)
 |  4
 |
 |  Built-in subclasses:
 |      bool
 |
 |  Methods defined here:
 |
 |  __abs__(self, /)
 |      abs(self)
 |
 |  __add__(self, value, /)
 |      Return self+value.
 |
 |  __and__(self, value, /)
 |      Return self&value.
 |
 |  __bool__(self, /)
 |      True if self else False
 |
 |  __ceil__(self, /)
 |      Ceiling of an Integral returns itself.
 |
 |  __divmod__(self, value, /)
 |      Return divmod(self, value).
 |
 |  __eq__(self, value, /)
 |      Return self==value.
 |
 |  __float__(self, /)
 |      float(self)
 |
 |  __floor__(self, /)
 |      Flooring an Integral returns itself.
 |
 |  __floordiv__(self, value, /)
 |      Return self//value.
 |
 |  __format__(self, format_spec, /)
 |      Convert to a string according to format_spec.
 |
 |  __ge__(self, value, /)
 |      Return self>=value.
 |
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |
 |  __getnewargs__(self, /)
 |
 |  __gt__(self, value, /)
 |      Return self>value.
 |
 |  __hash__(self, /)
 |      Return hash(self).
 |
 |  __index__(self, /)
 |      Return self converted to an integer, if self is suitable for use as an index into a list.
 |
 |  __int__(self, /)
 |      int(self)
 |
 |  __invert__(self, /)
 |      ~self
 |
 |  __le__(self, value, /)
 |      Return self<=value.
 |
 |  __lshift__(self, value, /)
 |      Return self<<value.
 |
 |  __lt__(self, value, /)
 |      Return self<value.
 |
 |  __mod__(self, value, /)
 |      Return self%value.
 |
 |  __mul__(self, value, /)
 |      Return self*value.
 |
 |  __ne__(self, value, /)
 |      Return self!=value.
 |
 |  __neg__(self, /)
 |      -self
 |
 |  __or__(self, value, /)
 |      Return self|value.
 |
 |  __pos__(self, /)
 |      +self
 |
 |  __pow__(self, value, mod=None, /)
 |      Return pow(self, value, mod).
 |
 |  __radd__(self, value, /)
 |      Return value+self.
 |
 |  __rand__(self, value, /)
 |      Return value&self.
 |
 |  __rdivmod__(self, value, /)
 |      Return divmod(value, self).
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  __rfloordiv__(self, value, /)
 |      Return value//self.
 |
 |  __rlshift__(self, value, /)
 |      Return value<<self.
 |
 |  __rmod__(self, value, /)
 |      Return value%self.
 |
 |  __rmul__(self, value, /)
 |      Return value*self.
 |
 |  __ror__(self, value, /)
 |      Return value|self.
 |
 |  __round__(self, ndigits=<unrepresentable>, /)
 |      Rounding an Integral returns itself.
 |
 |      Rounding with an ndigits argument also returns an integer.
 |
 |  __rpow__(self, value, mod=None, /)
 |      Return pow(value, self, mod).
 |
 |  __rrshift__(self, value, /)
 |      Return value>>self.
 |
 |  __rshift__(self, value, /)
 |      Return self>>value.
 |
 |  __rsub__(self, value, /)
 |      Return value-self.
 |
 |  __rtruediv__(self, value, /)
 |      Return value/self.
 |
 |  __rxor__(self, value, /)
 |      Return value^self.
 |
 |  __sizeof__(self, /)
 |      Returns size in memory, in bytes.
 |
 |  __sub__(self, value, /)
 |      Return self-value.
 |
 |  __truediv__(self, value, /)
 |      Return self/value.
 |
 |  __trunc__(self, /)
 |      Truncating an Integral returns itself.
 |
 |  __xor__(self, value, /)
 |      Return self^value.
 |
 |  as_integer_ratio(self, /)
 |      Return a pair of integers, whose ratio is equal to the original int.
 |
 |      The ratio is in lowest terms and has a positive denominator.
 |
 |      >>> (10).as_integer_ratio()
 |      (10, 1)
 |      >>> (-10).as_integer_ratio()
 |      (-10, 1)
 |      >>> (0).as_integer_ratio()
 |      (0, 1)
 |
 |  bit_count(self, /)
 |      Number of ones in the binary representation of the absolute value of self.
 |
 |      Also known as the population count.
 |
 |      >>> bin(13)
 |      '0b1101'
 |      >>> (13).bit_count()
 |      3
 |
 |  bit_length(self, /)
 |      Number of bits necessary to represent self in binary.
 |
 |      >>> bin(37)
 |      '0b100101'
 |      >>> (37).bit_length()
 |      6
 |
 |  conjugate(self, /)
 |      Returns self, the complex conjugate of any int.
 |
 |  is_integer(self, /)
 |      Returns True. Exists for duck type compatibility with float.is_integer.
 |
 |  to_bytes(self, /, length=1, byteorder='big', *, signed=False)
 |      Return an array of bytes representing an integer.
 |
 |      length
 |        Length of bytes object to use.  An OverflowError is raised if the
 |        integer is not representable with the given number of bytes.  Default
 |        is length 1.
 |      byteorder
 |        The byte order used to represent the integer.  If byteorder is 'big',
 |        the most significant byte is at the beginning of the byte array.  If
 |        byteorder is 'little', the most significant byte is at the end of the
 |        byte array.  To request the native byte order of the host system, use
 |        sys.byteorder as the byte order value.  Default is to use 'big'.
 |      signed
 |        Determines whether two's complement is used to represent the integer.
 |        If signed is False and a negative integer is given, an OverflowError
 |        is raised.
 |
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |
 |  from_bytes(bytes, byteorder='big', *, signed=False)
 |      Return the integer represented by the given array of bytes.
 |
 |      bytes
 |        Holds the array of bytes to convert.  The argument must either
 |        support the buffer protocol or be an iterable object producing bytes.
 |        Bytes and bytearray are examples of built-in objects that support the
 |        buffer protocol.
 |      byteorder
 |        The byte order used to represent the integer.  If byteorder is 'big',
 |        the most significant byte is at the beginning of the byte array.  If
 |        byteorder is 'little', the most significant byte is at the end of the
 |        byte array.  To request the native byte order of the host system, use
 |        sys.byteorder as the byte order value.  Default is to use 'big'.
 |      signed
 |        Indicates whether two's complement is used to represent the integer.
 |
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |
 |  __new__(*args, **kwargs)
 |      Create and return a new object.  See help(type) for accurate signature.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  denominator
 |      the denominator of a rational number in lowest terms
 |
 |  imag
 |      the imaginary part of a complex number
 |
 |  numerator
 |      the numerator of a rational number in lowest terms
 |
 |  real
 |      the real part of a complex number

Los números de punto flotante también tienen algunos métodos definidos. Por ejemplo podemos saber si un número flotante corresponde a un entero:

b = -3.0
b.is_integer()
True
c = 142.25
c.is_integer()
False

o podemos expresarlo como el cociente de dos enteros, o en forma hexadecimal

c.as_integer_ratio()
(569, 4)

Recordemos, como último ejemplo, los números complejos:

z = 1 + 2j
zc = z.conjugate()              # Método que devuelve el conjugado
zr = z.real                     # Componente, parte real
zi = z.imag                     # Componente, parte imaginaria
print(z, zc, zr, zi, zc.imag)
(1+2j) (1-2j) 1.0 2.0 -2.0

Tipos compuestos

En Python, además de los tipos simples (números y booleanos, entre ellos) existen tipos compuestos, que pueden contener más de un valor de algún tipo. Entre los tipos compuestos más importantes vamos a referirnos a:

  • Se pueden definir con comillas dobles ( ” ), comillas simples ( ’ ), o tres comillas (simples o dobles). Comillas (dobles) y comillas simples producen el mismo resultado. Sólo debe asegurarse que se utiliza el mismo tipo para abrir y para cerrar el string
    Ejemplo: s = "abc" (el elemento s[0] tiene el valor "a").
  • Las listas son tipos que pueden contener más de un elemento de cualquier tipo. Los tipos de los elementos pueden ser diferentes. Las listas se definen separando los diferentes valores con comas, encerrados entre corchetes. Se puede referir a un elemento por su índice.
    Ejemplo: L = ["a",1, 0.5 + 1j] (el elemento L[0] es igual al string "a").
  • Las tuplas se definen de la misma manera que las listas pero con paréntesis en lugar de corchetes. Ejemplo: T = ("a",1, 0.5 + 1j).
  • Los diccionarios son contenedores a cuyos elementos se los identifica con un nombre (key) en lugar de un índice. Se los puede definir dando los pares key:value entre llaves
    Ejemplo: D = {'a': 1, 'b': 2, 1: 'hola', 2: 3.14} (el elemento D['a'] es igual al número 1).

Strings: Secuencias de caracteres

Una cadena o string es una secuencia de caracteres (letras, “números”, símbolos).

Se pueden definir con comillas, comillas simples, o tres comillas (simples o dobles). Comillas simples o dobles producen el mismo resultado. Sólo debe asegurarse que se utilizan el mismo tipo para abrir y para cerrar el string

Triple comillas (simples o dobles) sirven para incluir una cadena de caracteres en forma textual, incluyendo saltos de líneas.

saludo = 'Hola Mundo'           # Definición usando comillas simples
saludo2 = "Hola Mundo"          # Definición usando comillas dobles
saludo, saludo2
('Hola Mundo', 'Hola Mundo')
saludo == saludo2
True

Los strings se pueden definir equivalentemente usando comillas simples o dobles. De esta manera es fácil incluir comillas dentro de los strings

otro = "that's all"
dijo = '"Cómo te va" dijo el murguista a la muchacha'
print(otro)
that's all
print(dijo)
"Cómo te va" dijo el murguista a la muchacha
respondio = "Le dijo \"Bien\" y lo dejó como si nada"
print(respondio)
Le dijo "Bien" y lo dejó como si nada

Para definir strings que contengan más de una línea, manteniendo el formato, se pueden utilizar tres comillas (dobles o simples):

Texto_largo = '''Aquí me pongo a cantar
  Al compás de la vigüela,
Que el hombre que lo desvela
  Una pena estraordinaria
Como la ave solitaria
  Con el cantar se consuela.'''
print (Texto_largo)
Aquí me pongo a cantar
  Al compás de la vigüela,
Que el hombre que lo desvela
  Una pena estraordinaria
Como la ave solitaria
  Con el cantar se consuela.
Texto_largo
'Aquí me pongo a cantarn  Al compás de la vigüela,nQue el hombre que lo desvelan  Una pena estraordinarianComo la ave solitarian  Con el cantar se consuela.'

En Python se puede utilizar cualquier caracter que pueda ingresarse por teclado, ya que por default Python-3 trabaja usando la codificación UTF-8, que incluye todos los símbolos que se nos ocurran. Por ejemplo:

# Un ejemplo que puede interesarnos un poco más:
label = "σ = λ T/ µ + π · δξ"
print('tipo de label: ', type(label))
print ('Resultados corresponden a:', label, ' (en m/s²)')
tipo de label:  <class 'str'>
Resultados corresponden a: σ = λ T/ µ + π · δξ  (en m/s²)

Operaciones

En Python ya hay definidas algunas operaciones que involucran strings como la suma (composición o concatenación) y el producto por enteros (repetición).

s = saludo + ' -> ' + otro + '\t'
s = s + "chau"
print (s) #  Suma de strings
Hola Mundo -> that's all    chau

Como vemos la suma de strings es simplemente la concatenación de los argumentos. La multiplicación de un entero n por un string es simplemente la suma del string n veces:

a = '1'
b = 1
print(a, type(a))
print(b, type(b))
1 <class 'str'>
1 <class 'int'>
print ("Multiplicación por enteros de strings:", 7*a)
print ("Multiplicación por enteros de enteros:", 7*b)
Multiplicación por enteros de strings: 1111111
Multiplicación por enteros de enteros: 7

La longitud de una cadena de caracteres (como de otros objetos) se puede calcular con la función len()

print ('longitud del saludo =', len(saludo), 'caracteres')
longitud del saludo = 10 caracteres

Por ejemplo, podemos usar estas operaciones para realizar el centrado manual de una frase:

titulo = "Centrado manual simple"
n = int((60-len(titulo))//2)    # Para un ancho de 60 caracteres
print ((n)*'<', titulo ,(n)*'>')
#
saludo = 'Hola Mundo'
n = int((60-len(saludo))//2)    # Para un ancho de 60 caracteres
print (n*'*', saludo, n*'*')
<<<<<<<<<<<<<<<<<<< Centrado manual simple >>>>>>>>>>>>>>>>>>>
********************* Hola Mundo *********************

Iteración y Métodos de Strings

Los strings poseen varias cualidades y funcionalidades. Por ejemplo:

  • Se puede iterar sobre ellos, y también quedarse con sólo una parte (slicing)

  • Tienen métodos (funciones que se aplican a su dueño)

Veamos en primer lugar cómo se hace para seleccionar parte de un string

Indexado de strings

Podemos referirnos a un caracter o una parte de una cadena de caracteres mediante su índice. Los índices en Python empiezan en 0.

s = "0123456789"
print ('Primer caracter  :', s[0])
print ("Segundo caracter :", s[1])
Primer caracter  : 0
Segundo caracter : 1

Si queremos empezar desde el final utilizamos índices negativos. El índice “-1” corresponde al último caracter.n

print ("El último caracter :", s[-1])
print ("El anteúltimo caracter :", s[-2])
El último caracter : 9
El anteúltimo caracter : 8

También podemos elegir un subconjunto de caracteres:

print ('Los tres primeros:          ', s[0:3])
print ('Todos a partir del tercero: ', s[3:])
print ('Los últimos dos:            ', s[-2:])
print ('Todos menos los últimos dos:', s[:-2])
Los tres primeros:           012
Todos a partir del tercero:  3456789
Los últimos dos:             89
Todos menos los últimos dos: 01234567

Estas “subcadenas” son cadenas de caracteres, y por lo tanto pueden utilizarse de la misma manera que cualquier otra cadena:

print (s[:3] + s[-2:])
01289

La selección de elementos y subcadenas de una cadena s tiene la forma general

s[i: f: p]

donde i, f, p son enteros. La notación se refiere a la subcadena empezando en el índice i, hasta el índice f recorriendo con paso p. Casos particulares de esta notación son:

  • Un índice simple. Por ejemplo s[2] se refiere al tercer elemento.

  • Un índice negativo se cuenta desde el final, empezando desde -1.

  • Si el paso p no está presente el valor por defecto es 1. Ejemplo: s[2:4] = s[2:4:1].

  • Si se omite el primer índice, el valor asumido es 0. Ejemplo: s[:2:1] = s[0:2:1] = s[0:2].

  • Si se omite el segundo índice, el valor asumido es el del último elemento. Ejemplo: s[1::1] = s[1:].

  • Notar que puede omitirse más de un índice. Ejemplo: s[::2] = s[0:-1:2].

print(s)
print(s[0:5:2])
print (s[::2])
print (s[::-1])
print (s[::-3])
0123456789
024
02468
9876543210
9630

Veamos algunas utilidades que se pueden aplicar sobre un string:

a = "La mar estaba serena!"
print(a)
La mar estaba serena!

Por ejemplo, en python es muy fácil reemplazar una cadena por otra usando el método de strings replace()

b = a.replace('e','a')
print(b)
La mar astaba sarana!

o separar las palabras:

print(a.split())
['La', 'mar', 'estaba', 'serena!']
print(a.split('s'))
['La mar e', 'taba ', 'erena!']

En este caso, tanto replace() como split() son métodos que ya están definidos para los strings.

Recordemos que un método es una función que está definida junto con el tipo de objeto. En este caso el string. Hay más información sobre todos los métodos de las cadenas de caracteres en: String Methods

Veamos algunos ejemplos más:

Buscar y reemplazar cosas en un string:

a.find('e')
7
a.find('x')
-1
a.find('e',8)
15
a.find('re')
16

El método find(sub[, start[, end]]) -> int busca el substring sub empezando con el índice start (argumento opcional) y finalizando en el índice end (argumento opcional, que sólo puede aparecer si también aparece start). Devuelve el índice donde inicial es substring.

Formato de strings

En python se le puede dar formato a los strings de distintas maneras. Vamos a ver dos opciones: - Uso del método format - Uso de “f-strings”

El método format() es una función que busca en el strings las llaves y las reemplaza por los argumentos. Veamos esto con algunos ejemplos:

a = 2024
m = 'Feb'
d = 6
s = "Hoy es el día {} de {} de {}".format(d, m, a)
print(s)
print("Hoy es el día {}/{}/{}".format(d,m,a))
print("Hoy es el día {0}/{1}/{2}".format(d,m,a))
print("Hoy es el día {2}/{1}/{0}".format(d,m,a))
Hoy es el día 6 de Feb de 2024
Hoy es el día 6/Feb/2024
Hoy es el día 6/Feb/2024
Hoy es el día 2024/Feb/6
raiz = "datos-{}-{}-{}.dat"
fname = raiz.format(a,m,d)
print(fname)
datos-2024-Feb-6.dat

Más recientemente se ha implementado en Python una forma más directa de intercalar datos con caracteres literales, mediante f-strings, que permite una sintaxis más compacta. Comparemos las dos maneras:

pi = 3.141592653589793
s1 = "El valor de π es {}".format(pi)
s2 = "El valor de π con cuatro decimales es {0:.4f}".format(pi)
print(s1)
print(s2)
El valor de π es 3.141592653589793
El valor de π con cuatro decimales es 3.1416
print("El valor de 2π con seis decimales es {:012.6f}".format(2*pi))
El valor de 2π con seis decimales es 00006.283185
print("El valor de 2π con seis decimales es {:12.6f}".format(2*pi))
El valor de 2π con seis decimales es     6.283185
print("Un valor con seis decimales es {:012.6f}".format(12345678.12345678))
Un valor con seis decimales es 12345678.123457

Para darle formato a números enteros usamos el caracter ‘d’ (decimal)

print("{:03d}".format(5))
print("{:3d}".format(5))
005
  5
t1 = "el valor de π es {pi}"
print(t1)
el valor de π es {pi}
t = f"el valor de π es {pi}"
print(t)
el valor de π es 3.141592653589793

Podemos obtener estos mismos resultados usando “f-strings”

print(f"el valor de π es {pi}")
print(f"El valor de π con cuatro decimales es {pi:.4f}")
print(f"El valor de 2π con seis decimales es {2*pi:012.6f}")
print(f"{5:03d}")
print(f"{5:3d}")
el valor de π es 3.141592653589793
El valor de π con cuatro decimales es 3.1416
El valor de 2π con seis decimales es 00006.283185
005
  5
fname = f"datos-{a}-{m}-{d}.dat"
print(fname)
datos-2024-Feb-6.dat

Conversión de tipos

Como comentamos anteriormente, y se ve en los ejemplos anteriores, uno no define el tipo de variable a-priori sino que queda definido al asignársele un valor (por ejemplo a=3 define a como una variable del tipo entero).

a = 3                           # a es entero
b = 3.                         # b es real
c = 3 + 0j                      # c es complejo
print ("a es de tipo {0}\nb es de tipo {1}\nc es de tipo {2}".format(type(a), type(b), type(c)))
print ("'a + b' es de tipo {0} y 'a + c' es de tipo {1}".format(type(a+b), type(a+c)))
a es de tipo <class 'int'>
b es de tipo <class 'float'>
c es de tipo <class 'complex'>
'a + b' es de tipo <class 'float'> y 'a + c' es de tipo <class 'complex'>

Si bien Python hace la conversión de tipos de variables en algunos casos, no hace magia, no puede adivinar nuestra intención si no la explicitamos.

print (1+'1')
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[58], line 1
----> 1 print (1+'1')


TypeError: unsupported operand type(s) for +: 'int' and 'str'

Sin embargo, si le decimos explícitamente qué conversión queremos, todo funciona bien

print (str(1) + '1')
print (1 + int('1'))
print (1 + float('1.e5'))
11
2
100001.0
# a menos que nosotros **nos equivoquemos explícitamente**
print (1 + int('z'))
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

Cell In[60], line 2
      1 # a menos que nosotros **nos equivoquemos explícitamente**
----> 2 print (1 + int('z'))


ValueError: invalid literal for int() with base 10: 'z'

Ejercicios 02 (b)

  1. Para Entregar: Para la cadena de caracteres:

s = '''Aquí me pongo a cantar
Al compás de la vigüela,
Que el hombre que lo desvela
Una pena estraordinaria
Como la ave solitaria
Con el cantar se consuela.'''
  • Forme un nuevo string de 10 caracteres que contenga los 5 primeros y los 5 últimos del string anterior s. Imprima por pantalla el nuevo string.

  • Forme un nuevo string que contenga los 10 caracteres centrales de s (utilizando un método que pueda aplicarse a otros strings también). Imprima por pantalla el nuevo string.

  • Cuente la cantidad de veces que aparecen los substrings es, la, que, co, en los siguientes dos casos: distinguiendo entre mayúsculas y minúsculas, y no distinguiendo. Imprima el resultado.

  • Cambie todas las letras “m” por “n” y todas las letras “n” por “m” en s. Imprima el resultado por pantalla.

  1. Utilizando funciones y métodos de strings en la cadena de caracteres:

    s1='En un lugar de la Mancha de cuyo nombre no quiero acordarme'
    
  • Obtenga la cantidad de caracteres.

  • Imprima la frase anterior pero con cada palabra empezando en mayúsculas.

  • Cuente cuantas letras ‘a’ tiene la frase, ¿cuántas vocales tiene?

  1. Centrado manual de frases

    1. Utilizando la función len() centre una frase corta en una pantalla de 80 caracteres. Utilice la frase: “Un ejercicio con cadena de caracteres”

    2. Agregue subrayado a la frase anterior

    3. Escriba una función que centre y subraye una frase dada como argumento. Se espera obtener algo así:

      Un ejercicio con cadena de caracteres
      -------------------------------------
      
    4. Repita el punto anterior utilizando métodos de strings


Tipos contenedores

En Python existen tipos compuestos (que pueden contener más de un valor). Uno de ellos que ya vimos es el tipo string que es una cadena de caracteres. Veamos otros:

Listas

Las listas son tipos compuestos. Se definen separando los elementos con comas, todos encerrados entre corchetes. En general las listas pueden contener diferentes tipos, y pueden no ser todos iguales, pero suelen utilizarse con ítems del mismo tipo. Algunas características:

  • Los elementos no son necesariamente homogéneos en tipo.

  • Los elementos están ordenados.

  • Se accede mediante un índice.

  • Están definidas operaciones entre Listas, así como algunos métodos

    • x in L (¿x es un elemento de L?)

    • x not in L (¿x no es un elemento de L?)

    • L1 + L2 (concatenar L1 y L2)

    • n*L1 (n veces L1)

    • L1*n (n veces L1)

    • L[i] (Elemento i-ésimo)

    • L[i:j] (Elementos i a j)

    • L[i:j:k] (Elementos i a j, elegidos uno de cada k)

    • len(L) (longitud de L)

    • min(L) (Mínimo de L)

    • max(L) (Máximo de L)

    • L.index(x, [i]) (Índice de x, iniciando en i)

    • L.count(x) (Número de veces que aparece x en L)

    • L.append(x) (Agrega el elemento x al final)

Veamos algunos ejemplos:

cuadrados = [1, 4, 9, 16, 25]

En esta línea hemos declarado una variable llamada cuadrados, y le hemos asignado una lista de cuatro elementos. En algunos aspectos las listas son muy similares a los strings. Se pueden realizar muchas de las mismas operaciones en strings, listas y otros objetos sobre los que se pueden iterar (iterables).

Las listas pueden accederse por posición y también pueden rebanarse (slicing)

Nota

La indexación de iteradores empieza desde cero (como en C)

cuadrados[0]
1
cuadrados[3]
16
cuadrados[-1]
25
cuadrados[:3:2]
[1, 9]
cuadrados[-2:]
[16, 25]

Los índices pueden ser positivos (empezando desde cero) o negativos empezando desde -1.

cuadrados:

1

4

9

16

25

índices:

0

1

2

3

4

índices negativos:

-5

-4

-3

-2

-1

Nota

La asignación entre listas no copia todos los datos

a = cuadrados
a is cuadrados
True
print("Valores originales")
print(a)
cuadrados[0]= -1
print("Valores modificados")
print(a)
print(cuadrados)
Valores originales
[1, 4, 9, 16, 25]
Valores modificados
[-1, 4, 9, 16, 25]
[-1, 4, 9, 16, 25]
a is cuadrados
True
cuadrados[0] = 1
b = cuadrados.copy()
print("Valores originales")
print(b)
print(cuadrados)
print("Valores modificados")
cuadrados[0]=-2
print(b)
print(cuadrados)
Valores originales
[1, 4, 9, 16, 25]
[1, 4, 9, 16, 25]
Valores modificados
[1, 4, 9, 16, 25]
[-2, 4, 9, 16, 25]

Comprensión de Listas

Una manera sencilla de definir una lista es utilizando algo que se llama Comprensión de listas. Como primer ejemplo veamos una lista de números cuadrados como la que escribimos anteriormente. En lenguaje matemático la defiríamos como \(S = \{x^{2} : x \in \{0 \dots 9\}\}\). En python es muy parecido.

Podemos crear la lista cuadrados utilizando compresiones de listas

L1 = [1,3,5]
L2 = [i**2 for i in L1]
L3 = [i+1 for i in L2]
print(L2)
[1, 9, 25]
L3
[2, 10, 26]
cuadrados = [i**2 for i in range(10)]
cuadrados
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Una lista con los cuadrados sólo de los números pares también puede crearse de esta manera, ya que puede incorporarse una condición:

L0 = [a**2 for a in range(2,21)]
print(L0)
[4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400]
L = [a**2 for a in range(2,21) if a % 2 == 0]
L
[4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

Tuplas

Las tuplas son objetos similares a las listas, sobre las que se puede iterar y seleccionar partes según su índice. La principal diferencia es que son inmutables mientras que las listas pueden modificarse.

L1 = [0,1,2,3,4,5] # Las listas se definen con corchetes
T1 = (0,1,2,3,4,5) # Las tuplas se definen con paréntesis
L1[0] = -1
print(f"L1[0] = {L1[0]}")
L1[0] = -1
T1[0] = -1
print(f"{T1[0] = }")
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[24], line 1
----> 1 T1[0] = -1
      2 print(f"{T1[0] = }")


TypeError: 'tuple' object does not support item assignment
try:
    T1[0] = -1
    print(f"{T1[0] = }")
except:
    print('Tuples son inmutables')
Tuples son inmutables

Las tuplas se usan cuando uno quiere crear una “variable” que no va a ser modificada. Además códigos similares con tuplas pueden ser un poco más rápidos que si usan listas.

Un uso común de las tuplas es el de asignación simultánea a múltiples variables:

a, b, c = (1, 3, 5)
print(b)
print(a, b, c)
3
1 3 5
# Los paréntesis son opcionales en este caso
a, b, c = 4, 5, 6
print(b)
print(a,b,c)
5
4 5 6

Un uso muy común es el de intercambiar el valor de dos variables

print(a,b)
a, b = b, a                     # swap
print(a,b)
4 5
5 4
a=3
t = (a,5)
t[0]
3
a=5
print(t)
(3, 5)

Rangos

Los objetos de tipo range representan una secuencia inmutable de números y se usan habitualmente para ejecutar un bucle for un número determinado de veces. El formato es uno de:

range(stop)
range(start, stop)
range(start, stop, step)
range(2)
range(0, 2)
type(range(2))
range
range(2,9)
range(2, 9)
list(range(2,9))
[2, 3, 4, 5, 6, 7, 8]
list(range(2,9,2))
[2, 4, 6, 8]
list("hola")
['h', 'o', 'l', 'a']
tuple("hola")
('h', 'o', 'l', 'a')