summaryrefslogtreecommitdiff
path: root/es/02_datos.md
blob: 48c802cb2134f2472471f1d6abde7ee56baff3b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
# Trabajando con datos

Programar es, principalmente, tratar con datos y los datos no son más que
piezas de información que se estructura de una forma concreta.

## Nombrando datos

Los datos se almacenan en la memoria principal de la computadora. Puedes
imaginarte la memoria como un conjunto de cajas identificadas con direcciones,
como si fuesen los buzones de un portal con muchos pisos. Para utilizar datos,
éstos se guardan en los diferentes cajones y se van extrayendo y actualizando.
La importancia de nombrar las cosas es evidente, si no guardamos un listado de
dónde hemos guardado qué no podemos recuperar los datos que hemos creado.

Al igual que en las matemáticas se utilizan los símbolos para representar
posibles valores y simplificar los procesos, en los lenguajes de programación
se utilizan símbolos para definir referencias a los datos y así poder referirse
a los mismos datos por su nombre sin tener que introducir su contenido
constantemente. Los lenguajes de programación aportan a quien programa la
facilidad de poder crear sus propios nombres para no tener que usar las
direcciones propias de la memoria, que normalmente son número sin más
significado que su posición, la mayor parte de las veces permitiendo a quien
programa abstraerse de estos detalles internos.

En python existe un operador para declarar símbolos que hacen referencia a un
valor: el operador `=`. Este operador enlaza el valor de la derecha con el
nombre de la izquierda. Con un ejemplo es más fácil de comprender. Probando en
la REPL:

``` python
>>> a = 10
>>> a
10
>>> a + 1
11
```

Como ves, en el primer paso se asocia el valor `10` al identificador `a` y más
adelante se puede utilizar `a` para hacer referencia al valor enlazado.

Las referencias han de ser declaradas antes de usarse, si no, el intérprete no
las conoce y lanza una excepción:

``` python
>>> b
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    b
NameError: name 'b' is not defined
```

> NOTA: Los nombres para poder ser interpretados correctamente por python deben
> cumplir unas normas estrictas:
>
> - No pueden tener espacios.
> - Sólo pueden estar formados por combinaciones de letras, números y el
>   símbolo `_`.
> - No pueden empezar por un dígito.

### Todo es una referencia

El comportamiento de estas referencias puede no ser intuitivo si vienes de
otros lenguajes. El operador `=` enlaza, como se dice anteriormente, un nombre
a un valor concreto, pero eso no significa que copie su contenido. En python
los valores y los nombres son conceptos independientes. Los valores ocupan su
lugar en la memoria y los nombres hacen referencia a dónde se encuentran estos
valores. Es muy común tener muchas referencias a la misma estructura de datos y
transformar su contenido desde todas las referencias.

A nivel técnico, lo que en python se conoce como variable y en este documento
hemos hecho el esfuerzo de llamar referencia es lo que en otros lenguajes se
conoce como un puntero y la labor del operador `=` es la de asignar el puntero
a la dirección donde se encuentran los datos a los que debe apuntar.

Volviendo a la metáfora de los buzones, en lenguajes de más bajo nivel como C
estás obligado a seleccionar qué buzón vas a utilizar para introducir cada
dato. Por lo que si cambia el tipo de dato a gestionar, puede que el buzón se
quede pequeño o que los datos se interpreten de forma incorrecta. Sin embargo,
python guarda las referencias de forma independiente a los valores y adecua el
número de buzones en uso al tamaño de los datos de los que dispones
reordenando, si es necesario, la estructura completa de valores y referencias.
A este concepto se le conoce como gestión automática de memoria (*automatic
memory management*).

En resumen, las referencias en python son únicamente un recordatorio que sirve
para poder acceder a un valor. Esto cobra importancia más adelante, y no vamos
a rehuir la responsabilidad de enfangarnos en ello.

## Tipos

Tal y como aclaraba el texto de la introducción, python tiene un sistema de
tipos dinámico (*dynamic type system*). Lo que significa que gestiona los tipos
de forma automática, permitiendo a los nombres hacer referencia a diferentes
tipos de valor durante la ejecución del programa a diferencia de otros
lenguajes como, por ejemplo, C, donde el tipo de las variables debe ser
declarado de antemano y no puede cambiarse.

Esto es posible debido al fenómeno explicado en el apartado anterior por un
lado, y, por el otro, a que los datos de python son un poco más complejos que
en otros lenguajes y guardan una pequeña nota que indica cómo deben ser
interpretados.

Si en algún momento se le pide a python que asigne un valor de un tipo distinto
al que una referencia tenía no habrá problemas porque es el propio dato quien
guarda la información suficiente para saber cómo entenderlo. Las referencias
sólo almacenan dónde se guarda este dato.

> NOTA: Seguramente te habrás dado cuenta de que el funcionamiento de python es
> más ineficiente que el de C o lenguajes similares pero mucho más flexible. Y
> así es. A la hora de elegir el lenguaje debemos valorar cuál nos interesa
> más para la labor que vamos a realizar.

### Tipos simples

Hemos denominado tipos simples a los que no empaquetan más de un valor
internamente. En otros lenguajes o contextos se les conoce como escalares.


#### La nada

La nada en python se representa con el valor `None` y es útil en innumerables
ocasiones.

#### Boolean

Los valores booleanos expresan *verdad* (`True`) o *mentira* (`False`) y sirven
para gestionar lógica desde esos términos. Mas adelante los veremos en acción.

#### Integer

Los Integer, números enteros en inglés, ya han aparecido anteriormente. Para
usar un número entero puedes introducirlo tal cual. Recuerda que hay enteros
positivos y negativos. El símbolo para marcar números negativos en python es el
`-`, que coincide con el operador de la resta.

``` python
>>> 14
14
>>> -900
-900
>>>
```

Los números enteros también pueden expresarse en otras bases, como en
hexadecimal. Dependiendo de la aplicación en la que te encuentres igual
necesitas mirarlo más en detalle:

``` python
>>> 0x10
16
```

#### Float

Los números Float, o de coma flotante, son números no-enteros. Ten cuidado
con ellos porque la coma flotante es peculiar.

El nombre coma flotante viene de que la coma no siempre se mantiene con la
misma precisión. En realidad estos números se guardan como los números en
notación científica (2,997E8 m/s para la velocidad de la luz, por ejemplo).
La notación científica siempre implica tener una coma, pero cuya posición se
varía con el exponente posterior.

El modo de almacenamiento de los números de coma flotante es muy similar a la
notación científica: se almacenan dos valores de tamaño fijo, la *mantisa* y el
*exponente* para, de este modo, poder ajustar la precisión en función del
tamaño del número almacenado. No es lo mismo expresar la velocidad de la luz,
2,997E8 m/s, que la longitud de onda del color rojo, 6,250E-7 m, ambos
valores tienen un tamaño muy distinto, pero usando dos *mantisas* de tamaño
similar, cuatro dígitos (2997 y 6250), y dos exponentes de tamaño similar, un
dígito (8 y -7), hemos expresado valores muy diferentes, ambos con la misma
precisión relativa.

El problema viene cuando nos apetece mezclarlos, por ejemplo, sumándolos.
Imagina que tienes dos valores de esas dimensiones, uno de millones y otro de
millonésimas partes de la unidad, y los quieres sumar entre ellos. Si sólo
tienes una mantisa limitada para representarlos, la suma resultará en el
redondeo de ambos a la precisión que tienes disponible. Es decir: el resultado
será el valor grande y el pequeño se perderá en el redondeo.

Aunque en este caso la suma es precisa, si tratas con un billón de valores
pequeños y uno grande y quieres obtener la suma de todos y los sumas en parejas
siempre con el grande, en cada suma se descartará el valor pequeño en el
redondeo y el resultado total será el valor grande que tenías. Sin embargo, si
sumas los valores de tamaño similar entre ellos primero, obtendrás un valor
suficientemente grande como para alterar el resultado de la suma contra el
valor grande al final, dando así un resultado distinto. Te recomiendo entonces,
que si te encuentras en una situación como ésta tengas cuidado y, por ejemplo,
ordenes los números de menor a mayor antes de sumarlos, para obtener una suma
de buena precisión.

En realidad, el exponente en el caso de python (y en casi todos los demás
lenguajes) no está elevando un 10 a la enésima potencia, si no que lo hace con
un 2. Por lo que lo expresado anteriormente es un poco distinto. Esto provoca
que algunos números de coma flotante no sean tan redondos como deberían (por
ejemplo, `2.999999999`, cuando debería ser `3.0`) y compararlos entre ellos
puede ser desastroso. Para evitarlo, te recomiendo que *siempre* redondees los
valores a una precisión que puedas controlar.

Aunque realmente es algo más complejo, lo que sabes ahora te evitará problemas
en el futuro, sobre todo cuando analices datos, uno de los sectores donde
python se usa de forma extensiva. Si necesitas saber más, debes investigar la
artimética de coma flotante, o *floating point arithmetic* en inglés.

Declarar números de coma flotante es natural porque usa una sintaxis a la que
estamos acostumbrados:

``` python
>>> 1E10
10000000000.0
>>> 1.0
1.0
>>> 0.2E10
2000000000.0
>>>
```


#### Complex

Python soporta números complejos y los expresa utilizando la letra `j`. Como
suponen un caso bastante concreto no los analizaremos en detalle. Pero tienes
disponible la documentación de python para lo que quieras.

``` python
>>> 1-23j
(1-23j)
```

#### String

Un String es una cadena de caracteres. Los Strings en python son, a diferencia
de en otros lenguajes, un escalar, al contrario de lo que la primera definición
que hemos expresado puede hacernos pensar. En python los Strings no son un
conjunto de caracteres alfanuméricos sueltos que se comportan como un valor, es
al revés. El concepto de carácter no existe y ha de expresarse con un String de
longitud 1.

Los strings se expresan rodeando texto con comillas dobles, `"`, o simples `'`
(el símbolo del apóstrofe).

``` python
>>> "Hola"
'Hola'
>>> 'Hola'
'Hola'
```

El hecho de que haya dos opciones para delimitar los strings facilita el
etiquetado como string de valores que contienen las propias comillas en su
contenido. También puede utilizarse la contrabarra `\` para cancelar la acción
de las comillas.


``` python
>>> "Tiene un apóstrofe: Luke's"
"Tiene un apóstrofe: Luke's"
>>> 'Tiene un apóstrofe: Luke's'
SyntaxError: invalid syntax
>>> 'Tiene un apóstrofe: Luke\'s'
"Tiene un apóstrofe: Luke's"
```

> NOTA: la contrabarra sirve para introducir caracteres especiales o caracteres
> de escape: `\n` salto de línea, `\t` tabulador, etc. Que son una herencia de
> los tiempos de las máquinas de escribir, pero son aún útiles y muy usados.
> Para expresar la propia contrabarra ha de escaparse a sí misma con otra
> contrabarra para que no evalúe el siguiente caracter como un caracter de
> escape.: `\\`.


### Tipos compuestos

Hemos denominado tipos compuestos a los que pueden incluir diferentes
combinaciones de tipos simples. Estos tipos dejan de ser escalares y se
comportan como vectores o conjuntos de datos. Estos tipos de dato pueden
contenerse a sí mismos, por lo que pueden crear estructuras anidadas complejas.

#### Tuple

Las tuplas o *tuple* en inglés son el tipo compuesto más sencillo en python.
Las tuplas definen un conjunto de valores de cualquier tipo.

Se declaran utilizando paréntesis añadiendo sus elementos separados por comas.
Y se accede a sus contenidos utilizando los corchetes e introduciendo el índice
del elemento que se quiere extraer.

``` python
>>> (2, 3, "HOLA")
(2, 3, 'HOLA')
>>> (2, 3, "HOLA")[0]
2
```
En python los índices comienzan en `0`.

#### List

Las listas o *list* son muy similares a las tuplas, pero son algo más complejas
porque pueden alterarse así mismas. A diferencia de todos los tipos que hemos
visto hasta ahora, tanto las listas como los diccionarios que veremos a
continuación son mutables. Esto significa que puede transformarse su valor. Más
adelante trataremos esto en detalle.

De momento, recuerda que las listas se construyen de forma similar a las
tuplas, pero utilizando corchetes en lugar de paréntesis. La forma de acceder a
los índices es idéntica.

``` python
>>> [2, 3, "HOLA"]
[2, 3, 'HOLA']
>>> [2, 3, "HOLA"][0]
2
```

#### Dictionary

Los diccionarios o *dictionary* son un tipo de dato similar a los dos
anteriores, pero que en lugar de utilizar índices basados en la posición de sus
elementos usan claves arbitrarias definidas por quien programa.

Además, los diccionarios no están ordenados así que no se puede suponer que las
claves siempre van a estar en el orden en el que se introducen.

Para declarar diccionarios es necesario indicar qué claves se quieren usar. Las
claves pueden ser de cualquier tipo que se considere *hasheable*[^hasheable],
concepto que se analiza más adelante, aunque normalmente su usan cadenas de
caracteres como claves.

El acceso a sus valores se realiza con los corchetes, del mismo modo que en las
listas, pero es necesario seleccionar la clave para acceder.

Los diccionarios, al igual que las listas, son mutables. Como veremos en
seguida.

``` python
>>> {"nombre": "Guido", "apellido": "Van Rossum", "popularidad": 8.0}
{'nombre': 'Guido', 'apellido': 'Van Rossum', 'popularidad': 8.0}
>>> {"nombre": "Guido", "apellido": "Van Rossum",
    "popularidad": 8.0}["popularidad"]
8.0
```

[^hasheable]: Los diccionarios son una implementación del concepto conocido
  como *hashmap* o *hash-table*. Su funcionamiento interno requiere que las
  claves puedan procesarse mediante una función de *hash*.

#### Set

Los *sets* son muy similares a las listas y tuplas, pero con varias
peculiaridades:

- Sus valores son únicos. No pueden repetirse.
- No están ordenados.
- No pueden accederse mediante los corchetes (`[]`).

Estas dos características tan estrictas, lejos de ser limitantes, aportan una
mejora radical en su rendimiento. Buscar elementos en un set es extremadamente
eficiente y se usan principalmente para esa labor.

Si quieres validar en algún momento que un valor pertenece a un conjunto de
valores, el set es el tipo de dato que estás buscando.

Los sets se declaran también usando las llaves, como un diccionario, pero no
usan claves.

``` python
>>> {"a", "b", 1}
{'a', 1, 'b'}
```

Otro de los usos más habituales de los sets es el de aplicar teoría de
conjuntos (*set* significa «conjunto»). Los sets pueden combinarse forma
eficiente mediante uniones (*union*), diferencias (*difference*),
intersecciones (*intersection*) y otros métodos descritos en esta teoría.

## Conversión

Ahora que conoces los valores sé que quieres saber cómo cambiar de uno a otro.
Cómo leer un Integer desde un String, etc. Python tiene funciones para
construir sus diferentes tipos de datos a partir de los diferentes inputs
posibles.

Aunque aún no sabes ejecutar funciones te adelanto cómo se hace con algunos
ejemplos:

``` python
>>> bool(1)
True
>>> bool(0)
False
>>> int("hola")
Traceback (most recent call last):
  File "<pyshell#27>", line 1, in <module>
    int("hola")
ValueError: invalid literal for int() with base 10: 'hola'
>>> int("10")
10
>>> float(10)
10.0
>>> complex(19)
(19+0j)
>>> str(10)
'10'
>>> tuple([1,2,3])
(1, 2, 3)
>>> list((1,2,3))
[1, 2, 3]
>>> dict((("a", 1),("b", 2)))
{'a': 1, 'b': 2}
>>> set([1,2,2,3,4,4,4,4,4])
{1, 2, 3, 4}
```

Los propios nombres de las funciones son bastante representativos de a qué tipo
convierten. Si quieres saber más puedes ejecutar `help(nombre)` y ver qué te
cuenta la ayuda.

> NOTA: Fíjate que si conviertes una secuencia de valores repetidos a *set*
> únicamente almacena los que no se repiten. Es uno de los usos más comunes que
> tienen.


## Operadores

Ahora que sabes el contexto en el que vas a jugar, necesitas poder alterar los
datos.

Existen operadores básicos que te permiten transformar los datos. Algunos ya
los has visto antes, como el operador `=`, que sirve para nombrar cosas, la
suma (`+`) o la resta (`-`), pero hay otros.

``` python
>>> 1 + 1
2
>>> 10 - 9
1
>>> 10 ** 2
100
>>>
```

Los siguientes apartados muestran algunos operadores que es interesante
memorizar.


### Pruebas de verdad

Las pruebas de verdad generan un valor booleano desde una pareja de valores. A
continuación una lista de las pruebas de verdad con unos ejemplos:

| operador | significado                               |
|----------|-------------------------------------------|
| `<`      | menor que                                 |
| `<=`     | menor o igual que                         |
| `>`      | mayor que                                 |
| `>=`     | mayor o igual que                         |
| `==`     | igual que                                 |
| `!=`     | diferente de                              |
| `is`     | identidad de objetos: «es»                |
| `is not` | identidad negada: «no es»                 |
| `in`     | comprobación de contenido: «en»           |
| `not in` | comprobación de contenido negada: «no en» |

``` python
>>> 1 > 1
False
>>> 1 >= 1
True
>>> 1 not in (0, 2, 3)
True
```

Aunque en otros lenguajes no es posible, la notación matemática habitual se
puede utilizar en python concatenando pruebas de verdad:

``` python
>>> x = 1.2
>>> 1 < x < 2
True
```

### Operadores lógicos

Los operadores lógicos mezclan booleanos y son muy importantes, sobre todo para
combinar las pruebas de verdad.

| operador | significado |
|----------|----------------------------------------------------------|
| `and`    | «Y» lógico, es `True` si todos sus operandos son `True`  |
| `or`     | «O» lógico, es `True` si algún operando es `True`        |
| `not`    | «No» lógico, invierte el operando                        |

La mayor parte de los operadores son binarios (como la suma), necesitan dos
valores y devuelven otro, pero existe al menos una excepción que funciona con
un único valor. El operador `not` sirve para darle la vuelta a un Booleano.

``` python
>>> not True
False
>>> True and True
True
>>> False and True
False
>>> False or True
True
>>> 1 > 0 and 2 > 1
True
```

### Matemáticos

Los operadores matemáticos transforman números entre ellos. Casi todos son
conocidos por su operación matemática.

| operador | significado |
|----------|----------|
| `+`      | Suma     |
| `-`      | Negativo o resta|
| `*`      | Multiplicación |
| `/`      | División |
| `**`     | Potencia |
| `%`      | Resto    |

### Operador ternario

Existe además un operador que puede usar tres parámetros, el *inline-if* (*if
en línea*), o *ternary operator*[^ternary]. El *ternary operator* se comporta
así:

``` python
>>> 1 if 1 > 9 else 9
9
>>> 1 if 1 < 9 else 9
1
```

[^ternary]: En realidad, el nombre de operador ternario no indica nada más que
  el hecho de que use tres argumentos. Históricamente se ha usado este nombre
  para este operador en concreto, que en otros lenguajes aparece con la forma
  `condición ? resultado1 : resultado2`, porque no solía existir ningún otro
  operador que recibiera tres argumentos.


### Operación en función del tipo

Python simplifica muchas tareas transformando el comportamiento de los
operadores en función del tipo de dato sobre el que trabajan. Los operadores
matemáticos están preparados para trabajar sobre números (de cualquier tipo)
pero la verdad es que algunos pueden ejecutarse sobre otros formatos. Por
ejemplo:

``` python
>>> "a" + "a"
'aa'
>>> [1,2] + [3,4]
[1, 2, 3, 4]
```

Esto se debe a que la funcionalidad del operador `+` ha sido diseñada para
operar de forma especial en Strings y en Listas, haciendo una concatenación.

En el futuro, cuando aprendas a diseñar tus propios tipos podrás hacer que los
operadores les afecten de forma especial, tal y como pasa aquí.

### Precedencia

La precedencia en python es muy similar a la matemática y usa las mismas reglas
para marcarla de forma explícita. Recuerda, en matemáticas se utilizan los
paréntesis para esto.

Los operadores siempre trabajan con sus correspondientes valores y python los
resuelve de forma ordenada. Si generas una operación muy compleja, python la
irá desgranando paso a paso y resolviendo las parejas una a una, cuanto más te
acostumbres a hacerlo en tu mente menos errores cometerás.

``` python
>>> 8 + 7 * 10 == (8 + 7) * 10
False
>>> 8 + 7 * 10
78
>>> (8 + 7) * 10
150
>>> 78 == 150
False
```

## Mutabilidad

Ya adelantamos que el operador `=` sirve para nombrar cosas. Ahora vamos a
combinar esa propiedad con la mutabilidad, o la propiedad de las cosas de
alterarse a sí mismas. Empecemos con un ejemplo:

``` python
>>> a = 10
>>> b = a + 10
>>> b
20
>>> a = b
>>> a
20
```

En este ejemplo hacemos que `a` haga referencia al valor 10, y después creamos
`b` a partir de `a` y otro 10. Gracias a la precedencia, primero se resuelve el
lado derecho completo obteniendo un 20 y después se asigna la referencia `b` a
ese valor.

Si queremos, después podemos reasignar el símbolo `a` a otro valor nuevo, en
este caso al que hacer referencia `b`, que es 20.

En este primer ejemplo, ningún valor está siendo alterado, si te fijas, sólo
estamos creando nuevos valores y cambiando las referencias a éstos.

Con los datos mutables podemos alterar los valores. Lo vemos con otro ejemplo:

``` python
>>> a = {}
>>> b = a
>>> a["cambio"] = "hola!"
>>> b
{'cambio': 'hola!'}
```

Primero creamos un diccionario vacío y le asignamos la referencia `a`. Después
le asignamos la referencia `b` a quien referenciaba `a`, es decir, al
diccionario vacío. Ambas referencias apuntan al mismo dato. Si después usamos
alguna alteración en el diccionario `a` como asignarle una nueva clave, `b`,
que hace referencia al mismo diccionario, también ve los cambios. Esto mismo
podría hacerse si se tratara de listas, ya que tienen la capacidad de alterarse
a sí mismas, pero nunca podría hacerse en tuplas, porque son inmutables.

> NOTA: Los diccionarios y las listas soportan un montón de funciones y
> alteraciones, no se mencionan en este apartado porque nunca terminaría. Se
> dejan para el futuro y para los ejemplos que se verán durante el documento.

Si intentásemos un ejemplo similar en tupla, no nos dejaría:

``` python
>>> a = ()
>>> b = a
>>> a[0] = 1
Traceback (most recent call last):
  File "<pyshell#67>", line 1, in <module>
    a[0] = 1
TypeError: 'tuple' object does not support item assignment
```

Ten cuidado cuando trates con elementos mutables, sobre todo si tienen muchas
referencias, porque puede que estés alterando los valores en lugares que no te
interesa. Para evitar este tipo de problemas, puedes generar copias de los
objetos, pero el proceso es poco eficiente y tedioso.

En este segundo caso, creamos una copia de `a` para que `b` sea independiente
de los cambios que ocurran en ésta. Aquí ya no estamos haciendo referencia
desde `b` a los datos que había en `a`, sino a una copia de éstos, almacenada
en otro lugar.

``` python
>>> a = {}
>>> b = dict(a)
>>> a["cambio"] = "hola!"
>>> b
{}
>>> a
{'cambio': 'hola!'}
```

## Lo que has aprendido

En este apartado has conocido los tipos fundamentales de python y cómo
convertir de uno a otro. Además, al conocer los operadores y su funcionamiento
ya eres más o menos capaz de usar python como una calculadora.

Además, has tenido ocasión de entender de forma superficial varios conceptos
avanzados como el manejo automático de memoria, el tipado dinámico y los
números de coma flotante, que son muy interesantes a la hora de trabajar porque
te permiten comprender la realidad que tienes a tu alrededor.