sábado, 3 de julio de 2021

Estructuras de datos Python

Listas

Probablemente la estructura de datos más fundamental en Python es la lista. Una lista es simplemente un array ordenado. (Es similar a lo que en otros lenguajes podría llamarse un array, pero con algunas funciones adicionales.)

lista_enteros = [1, 2, 3]

lista_heterogenea = ["cadena", 0.1, True]

lista_de_listas = [lista_enteros, lista_heterogenea,[]]

longitud_lista = len(lista_enteros) # igual a 3

suma_lista = sum(lista_enteros) # igual a 6

Estructuras de datos Python
Simetría

Podemos obtener o establecer el enésimo elemento de una lista con corchetes:

x = range(10) # es la lista [0, 1, ..., 9]

cero = x[0] # igual a 0, las listas son  0-indexadas

uno = x[1] # igual a 1

nueve = x[-1] # igual a 9, lee desde el último elemento

ocho = x[-2] # iguala 8,

x[0] = -1 # ahora x es [-1, 1, 2, 3, ..., 9]

También podemos utilizar corchetes para "dividir" listas:

primeros_tres = x[:3] # [-1, 1, 2]

tres_hasta_final = x[3:] # [3, 4, ..., 9]

uno_al_cuatro = x[1:5] # [1, 2, 3, 4]

ultimos_tres = x[-3:] # [7, 8, 9]

sin_primero_y_ultimo = x[1:-1] # [1, 2, ..., 8]

copia_de_x = x[:] # [-1, 1, 2, ..., 9]

Python tiene un operador in para verificar la pertenencia a la lista:

1 in [1, 2, 3] # True

0 in [1, 2, 3] # False

 

Esta verificación implica examinar los elementos de la lista de uno en uno, lo que significa que probablemente no deberíamos usarlo a menos que sepamos que nuestra lista es bastante pequeña. Es fácil concatenar listas:

x = [1, 2, 3]

x.extend([4, 5, 6]) # x es ahora [1,2,3,4,5,6]

Si no deseamos modificar x, podemos utilizar la adición de lista:

x = [1, 2, 3]

y = x + [4, 5, 6] # y es [1, 2, 3, 4, 5, 6]; x no cambia

Con más frecuencia, agregaremos a las listas un elemento a la vez:

x = [1, 2, 3]

x.append(0) # x es ahora[1, 2, 3, 0]

y = x[-1] # igual a  0

z = len(x) # igual a  4

A menudo es conveniente descomprimir las listas si sabemos cuántos elementos contienen:

x, y = [1, 2] # ahora x es 1, y es 2

Aunque obtendremos un ValueError si no tenemos el mismo número de elementos en ambos lados.

Es común usar un guión bajo para un valor que vamos a desechar:

_, y = [1, 2] # ahora y == 2, no nos importa el primer elemento

Tuplas

Las tuplas son primos de las listas. Prácticamente cualquier cosa que podamos hacer con una lista que no implique modificarla, podremos hacerlo en una tupla. Especificamos una tupla usando paréntesis (o nada) en lugar de corchetes:

mi_lista = [1, 2]

mi_tupla = (1, 2)

otra_tupla = 3, 4

mi_lista[1] = 3 # mi lista es ahora [1, 3]

try:

mi_tupla[1] = 3

except TypeError:

print "no se puede modificar una tupla"

Las tuplas son una forma conveniente de devolver múltiples valores de funciones:

def suma_y_multiplica(x, y):

return (x + y),(x * y)

sp = suma_y_multiplica(2, 3) # igual a (5, 6)

s, p = suma_y_multiplica(5, 10) # s es 15, p es 50

Las tuplas (y listas) también se pueden usar para asignaciones múltiples:

x, y = 1, 2 # ahora x es 1, y es 2

x, y = y, x # intercambiamos variables; ahora x es 2, y es 1

Diccionarios

Una estructura de datos fundamental es un diccionario, que asocia pares clave-valor y nos permite recuperar rápidamente el valor correspondiente a una clave determinada:

dic_vacio = {}

dic_edades = {"Antonio" : 34, "Juan" : 67 }

Podemos buscar el valor de una clave utilizando corchetes:

edad_antonio = dic_edades["Antonio"]  # devuelve 34

Pero obtendrermos un KeyError si intentamos recuperar una clave que no existe en el diccionario:

try:

edad_elena = dic_edades["Elena"]

except KeyError:

print "¡no existe la clave Elena!"

Podemos verificar la existencia de una clave usando:

antonio_existe_en_dic = "Antonio" in dic_edades # True

elena_existe_en_dic = "Elena" in dic_edades # False

Los diccionarios tienen un método get que devuelve un valor predeterminado (en lugar de generar una excepción) cuando buscamos una clave que no está en el diccionario:

edad_antonio = dic_edades.get("Antonio", 0) #devuelve 34

edad_elena = dic_edades.get("Elena", 0) # devuelve 0

 

Asignamos pares clave-valor utilizando los mismos corchetes:

dic_edades ["Juan"] = 68 # reemplaza el valor anterior

dic_edades ["Elena"] = 42 # añade un tercer valor

num_persona = len(dic_edades) # devuelve 3

Con frecuencia utilizaremos los diccionarios como una forma sencilla de representar datos estructurados:

dic_post_python = {

"autor" : "José Pedro",

"texto" : "Entrada de diccionarios con python",

"visitas" : 100,

"hashtags" : ["#python", "#diccionarios", "#datos"]

}

Además de buscar claves específicas, podemos mirarlas todas:

claves_dic = dic_post_python.keys() # lista de claves

valores_dic = dic_post_python.values() # lista de valores

pares_dic = dic_post_python.items() # lista de pares (clave,valor)

 

"autor" in dic_post_python # devuelve True

"José Pedro" in valores_dic # devuelve True

Valor = dic_post_python.get("autor")# devuelve José Pedro

#Para saber si existe una clave en el diccionario

existeClave = dic_post_python.has_key("autor")#devuelve True

Las claves del diccionario deben ser inmutables; en particular, no podemos utilizar listas como claves. Si necesitamos una clave de varias partes, debemos utilizar tuplas o encontrar una manera de convertir la clave en una cadena.

defaultdict

Imaginemos que estamos intentando contar las palabras de un documento. Un enfoque obvio es crear un diccionario en el que las claves sean palabras y los valores sean recuentos. Comprobamos cada palabra, y si ya existe  incrementamos su recuento y si no está la añadimos al diccionario:

contador_palabras = {}

for palabra in documento:

if palabra in contador_palabras:

contador_palabras[palabra] += 1

else:

contador_palabras[palabra] = 1

También podemos utilizar un enfoque diferente y manejar la excepción de intentar buscar una clave faltante:

contador_palabras = {}

for palabra in documento:

try:

contador_palabras [palabra] += 1

except KeyError:

contador_palabras[palabra] = 1

Un tercer enfoque es usar get, que se comporta de forma más elegante con las claves faltantes:

contador_palabras = {}

for palabra in documento:

cuenta_previa = contador_palabras.get(palabra, 0)

contador_palabras[palabra] = cuenta_previa + 1

Defaultdict es útil cuando intentamos buscar una clave, que aún no existe en el diccionario, En ese caso,  primero se agrega con un valor por defecto inicial que se le pasa por argumento al crearla. Para utilizar los defaultdict predeterminados, debemos importarlos desde colecciones:

from collections import defaultdict

contador_palabras = defaultdict(int) # int() produce 0

for palabra in documento:

contador_palabras [palabra] += 1

También pueden ser útiles con list o dict o incluso con nuestras propias funciones:

dd_lista = defaultdict(list) # list() produce una lista vacía

dd_lista[2].append(1) # ahora dd_lista contiene {2: [1]}

dd_dicc = defaultdict(dict) # dict() produce un diccionario vacío

dd_dicc["Jose"]["Ciudad"] = "Madrid" # { "Jose" : { "Ciudad" : Madrid"}}

dd_par = defaultdict(lambda: [0, 0])

dd_par[2][1] = 1 # ahora dd_par contiene {2: [0,1]}

Estos serán útiles cuando utilicemos diccionarios para "recopilar" resultados por alguna clave y no queramos tener que comprobar cada vez para ver si la clave existe aún.

Counter

Un contador convierte una secuencia de valores en claves de mapeo de objetos similares a defaultdict (int) para contar. Lo utilizaremos principalmente para crear histogramas:

from collections import Counter

c = Counter([0, 1, 2, 0]) # c es (basicamente) { 0 : 2, 1 : 1, 2 : 1 }

Esto nos da una forma muy sencilla de resolver nuestro problema de recuentos de palabras:

contador_palabras = Counter(documento)

Una instancia de Counter tiene un método most_common que suele ser útil:

 # imprime las 10 palabras más communes y su conteo

for palabra, conteo in contador_palabras.mas_comun(10):

print palabra, conteo

 

Sets

Se establece otra estructura de datos, que representa una colección de elementos distintos:

s = set()

s.add(1) # s es ahora { 1 }

s.add(2) # s es ahora { 1, 2 }

s.add(2) # s es todavía { 1, 2 }

x = len(s) # igual a 2

y = 2 in s # es True

z = 3 in s # es False

Utilizaremos sets por dos razones principales. La primera es que los sets son muy muy rápidos. Si tenemos una gran cantidad de elementos que queremos utilizar para una prueba, un set es más apropiado que una lista:

lista_palabras = ["a","abaco","abad" … "zurrón","zutano"]

"alguno" in lista_palabras # False, pero chequea cada elemento de la lista

set_palabras = set(lista_palabras)

"alguno" in set_palabras # chequea mucho más rápido

La segunda razón es encontrar los elementos distintos en una colección:

lista_objetos = [1, 2, 3, 1, 2, 3]

num_objetos = len(lista_objetos) # 6

set_objetos = set(lista_objetos) # {1, 2, 3}

num_objetos_distintos = len(set_objetos) # 3

lista_objetos_distintos = list(set_objetos) # [1, 2, 3]

Utilizaremos sets con mucha menos frecuencia que los diccionarios y las listas.


sábado, 26 de junio de 2021

AWS in a nutshell 18: Estrategias de migración

AWS tiene multitud de herramientas para migrar a su nube nuestros sistema, aquí daremos un vistazo general a dichas herramientas. Para migrar desde otros entornos disponemos de AWS Migration Hub. Tenemos dos tipos de migraciones las  heterogéneas y las homogéneas. 

Utilizaremos CloudEndure para las migraciones y veremos cómo instalar el Agente CloudEndure en una máquina Linux o Windows. Utilizaremos  el editor de  comandos CLI  de AWS para importar una imagen de una máquina virtual VM, y veremos el papel de AWS Snowball  durante las migraciones en la nube 

18.1 Opciones de migración

Las estrategias de migración comienzan con la determinación de qué servicios de TI existentes ya no son necesarios. También podemos emplear la estrategia de migración de reubicación, lo que significa que estamos reutilizando nuestra configuración existente. Aunque a veces tendremos que refactorizar, o rediseñar. Debemos considerar los nuevos servicios en la nube, ya sea infraestructura, plataforma o software como un espacio de servicio. 

18.2 Herramientas de Migración

Existe la herramienta de preparación para la adopción de la nube de AWS, también llamada CART

Consiste en un cuestionario de 16 preguntas que, basado en las respuestas, nos dará una estrategia general para lo que podríamos considerar al migrar a la nube. 

Luego está la herramienta CloudEndure y AWS Migration Hub y también AWS Application Discovery Service.  Está diseñada para buscar u obtener información sobre las aplicaciones que se ejecutan en nuestros centros de datos locales para determinar cuáles son buenos candidatos para la migración a la nube. Algunos servicios como el Servicio de migración de bases de datos de Amazon, o DMS,  no migrarán todo acerca de nuestro esquema, cosas como índices o vistas secundarias , triggers y procedimientos almacenados. 

Amazon Database Migration

Para eso, necesitaremos migrarlos utilizando herramientas especializadas como la Herramienta de conversión de esquemas de AWS o SCT 


Herramientas de migración AWS

18.3 Evaluación de la migración (CART)

AWS Cloud Adoption Readiness Tool, también llamada CART, no es una herramienta que muestre detalles técnicos sobre qué se debe migrar a la nube y cómo. En cambio, es un proceso general que realmente evalúa la actitud y la postura de una organización en relación con la adopción de la nube. Al Iniciar la evaluación, se nos harán 16 preguntas en diferentes categorías para generar un informe en .pdf. 

18.4 AWS Migration Hub 

Podemos usar la herramienta AWS Migration Hub para migrar servidores y aplicaciones a la nube de AWS.  La abrimos desde la consola de administración de AWS escribiendo  AWS Migration Hub desde donde podremos ver las opciones Descubrir, Evaluar y Migrar.

AWS Migration Hub


18.5 Migraciones CloudEndure

CloudEndure es una herramienta que se puede utilizar para migraciones, específicamente para migraciones de elevación y desplazamiento, lo que realmente significa mover servicios de las instalaciones locales a la nube, como máquinas virtuales. CloudEndure tiene alta disponibilidad. Fuera de la migración, podemos usarlo para mantener una copia sincronizada de datos en la nube de AWS para que haya sincronización entre nuestras instalaciones y la nube. Las migraciones de CloudEndure son aplicables para hosts locales de Windows y Linux, ya sean servidores físicos o máquinas virtuales.

18.6 VM requisitos de Imagen

Podemos usar la CLI de AWS para importar archivos de disco duro de máquina virtual que quizás ya hayamos tenido en funcionamiento localmente, puede que estén basados en Hyper-V o VMware, etc. Lo primero que debemos hacer es cargar el archivo o los archivos del disco VM en un depósito S3. Lo siguiente es crear un rol llamado vmimport. que utilizaremos de manera predeterminada cuando utilicemos CLI para importar un disco VM. 

18.7 Importación de VM

Para reutilizar nuestra inversión en máquinas virtuales, es posible importarlas a Amazon Web Services y lanzar instancias EC2 desde ellas. Para importar un archivo de disco de máquina virtual debemos asegurarnos de que exista dentro de un depósito S3.

18.8 Snowball y Snowmobile

Algunos clientes tendrán tantos datos  que  no es posible copiarlos a través de la red en AWS, incluso con las conexiones a Internet más rápidas. Ahí es donde entra en juego AWS Snowball. Esto se hace mediante un proceso en el cual AWS enviará uno o más dispositivos de almacenamiento seguros al centro de datos. Los datos se colocan en él, se cifran, y luego se envían de vuelta a AWS para que los copien en S3. Se usa para petabytes de datos locales que deben enviarse a la nube de AWS.  AWS Snowmobile. es para exabytes de datos.

Snowmobile es un contenedor de transferencia de 15 metros que se envía a la ubicación. 

los datos se copian en Snowmobile. Y luego Snowmobile es transportado físicamente a un centro de datos de AWS.