Hola, me llamo Luis y para hoy les traigo un post.
Índice
Mutable, inmutable… ¡todo es objeto!
En primer lugar, todo en Python es un objeto: las cadenas son objetos, las listas son objetos, las funciones son objetos, incluso los módulos son objetos.
Todo es un objeto en la forma en que todo se puede asignar a una variable o pasar como argumento a una función, y casi todo tiene métodos y atributos .
Los objetos en Python se pueden asignar a variables, colocar en listas, almacenar en diccionarios, pasar como argumentos, etc.
Algunos de estos objetos son mutables, lo que significa que puede cambiar su contenido sin cambiar su identidad. Sin embargo, existen inmutables como enteros o flotantes que no se pueden cambiar.
A continuación profundizaremos en el tema.
Todo en Python es un objeto. Y lo que todo recién llegado a Python debería aprender rápidamente es que todos los objetos en Python pueden ser mudable o inmutable.
Un objeto de tipo inmutable no se puede cambiar. Cualquier intento de modificar el objeto resultará en la creación de una copia.
Esta categoría incluye: números enteros, flotantes, complejos, cadenas, bytes, tuplas, rangos y conjuntos de imágenes.
Si cambia, entonces este es otro objeto. (Algunos dicen que esta es en realidad la dirección de memoria del objeto, pero ten cuidado con ellos, son del lado oscuro de la fuerza …)
>>> a = 1 >>> id(a) 140128142243264 >>> a += 2 >>> a 3 >>> id(a) 140128142243328
Así como puede conocer la identidad de un objeto, también podemos identificar qué tipo de datos es un objeto, usamos la función type ()
:
>>> type(“Holberton”) <class ‘str’> >>> a = 10 >>> type(a) <class ‘int’> >>> type((1,2)) <class ‘tuple’>
De acuerdo, 1
no es 3 ...
Noticias de última hora … Quizás no. Sin embargo, este comportamiento a menudo se olvida cuando se trabaja con tipos más complejos, especialmente cadenas.
>>> stack = "Overflow" >>> stack 'Overflow' >>> id(stack) 140128123955504 >>> stack += 'rocks!' >>> stack Overflow rocks! >>> id(stack) 140128123911472
Si bien parece que podemos cambiar la cadena nombrada por las variables stack, lo que realmente hacemos es crear un nuevo objeto para contener el resultado de la concatenación.
Nos engañan porque en el proceso, el objeto antiguo no va a ninguna parte, por lo que se destruye. En otra situación, eso habría sido más obvio:
>>> stack = "Stack" >>> stackOverflow = stack + "Overflow" >>> id(stack) 140128069348184 >>> id(stackOverflow) 140128123911480
En este caso, está claro que si queremos mantener la primera cadena, necesitamos una copia. ¿Pero es eso tan obvio para otros tipos?
Un objeto de tipo mutable puede y se cambia en su lugar. No se realizan copias implícitas.
Esta categoría incluye: listas, diccionarios, bytearrays y conjuntos.
Sigamos jugando con nuestra pequeña función de identificación.
Cuando un desarrollador necesita considerar la mutabilidad es cuando pasa argumentos a una función. Esto es muy importante, ya que determinará la capacidad de la función para modificar objetos que no pertenecen a su ámbito, o en otras palabras, si la función tiene efectos secundarios.
Esto también es importante para comprender dónde debería estar disponible el resultado de una función.
>>> def list_add3(lin): lin += [3] return lin >>> a = [1, 2, 3] >>> b = list_add3(a) >>> b [1, 2, 3, 3] >>> a [1, 2, 3, 3]
El error aquí es pensar que lin
, como parámetro de función, se puede modificar localmente. En su lugar, lin
y hace referencia al mismo objeto.
Debido a que este objeto es mutable, la modificación se realiza en el lugar, lo que significa que el objeto al que hacen referencia lin
y a
se modifica.
No es necesario devolver lin
, porque ya tenemos una referencia a este objeto en forma de a.a
y b
terminan refiriéndose al mismo objeto.
Esto no es lo mismo para las tuplas.
>>> def tup#ifndef NSMALLPOSINTS #define NSMALLPOSINTS 257 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 #endif #if NSMALLNEGINTS + NSMALLPOSINTS > 0 /* References to small integers are saved in this array so that they can be shared. The integers that are saved are those in the range -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). */ static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; #endif #ifdef COUNT_ALLOCS Py_ssize_t quick_int_allocs; Py_ssize_t quick_neg_int_allocs; #endif of the function, tin and referenced the same object. But this is an immutable object. So when the function tries to modify it, it receives a new object with the modification, while a keeps a reference to the original object. In this case, returning tin is mandatory, or the new object would be lost.
Ahora una tarea para ti:
- Cree dos variables con valores entre
-5
y256
y luego verifique si hacen referencia al mismo objeto. - Haga lo mismo pero usando valores para las variables fuera del rango anterior.
¿Qué pasó?
En Python, al iniciarse, Python3 mantiene una matriz de objetos enteros, de -5
a 256
. Por ejemplo, para el objeto int
, se utilizan macros llamadas NSMALLPOSINTS
yNSMALLNEGINTS
.
Veamos el código fuente:
#ifndef NSMALLPOSINTS #define NSMALLPOSINTS 257 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 #endif #if NSMALLNEGINTS + NSMALLPOSINTS > 0 /* References to small integers are saved in this array so that they can be shared. The integers that are saved are those in the range -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). */ static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; #endif #ifdef COUNT_ALLOCS Py_ssize_t quick_int_allocs; Py_ssize_t quick_neg_int_allocs; #endif
¿Qué significa esto?
Esto significa que cuando crea un int desde el rango de -5
y 256
, en realidad está haciendo referencia al objeto existente.
Esto se hace para evitar volver a crear objetos de uso común y porque de esa manera se puede representar cualquier carácter ASCII
.
¿Por qué eso importa?
Al asignar variables, es fundamental comprender las diferencias entre objetos mutables e inmutables. Los objetos mutables tienen bastantes trucos bajo la manga.
Sin embargo, si asignamos y así:
¿En qué es y el mismo objeto que x? ¿Pensé que eran objetos diferentes antes? Bueno, la forma en que ocurren las asignaciones en Python es crucial.
En este caso, no hemos asignado un nuevo objeto a y
, sino que le hemos asignado un alias a x
, lo que significa que le hemos dado la misma referencia que x
. Dado que ahora ambos apuntan al mismo objeto, un cambio en uno afectará al cambio en otro:
Pero, ¿qué pasa si quieres hacer una copia de tu objeto sin preocuparte por alterar tu original? Así como la división de objetos inmutables devuelve un nuevo objeto, también lo hace la división de listas:
Agregar a listas también puede ser un poco complicado. En Python, todo lo que se evalúa en el lado derecho de la expresión de asignación se hace referenciado por lo que está en el lado izquierdo de la expresión. Por lo tanto:
Se crea una nueva lista ya que el lado derecho de la tarea se evaluó como [1, 2, 3, 4, 5]
y ese nuevo objeto se hizo referenciado por l
. Sin embargo, si lo hacemos:
Podemos ver que el objeto sigue siendo el mismo. Esto se denomina asignación en el lugar y equivale a agregar a la lista. Sin embargo, dado que los objetos inmutables no se pueden cambiar, las expresiones a + = b
y a = a + b
funcionarían de la misma manera, similar a nuestro ejemplo anterior "Hola mundo"
.
Eso sería todo. Gracias por leer este post.
Añadir comentario