Internals del Modelo de Objetos de Python

PythonPythonBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Esta sección presenta más detalles sobre el modelo interno de objetos de Python y discute algunos temas relacionados con la gestión de memoria, la copia y la comprobación de tipos.

Asignación

Muchas operaciones en Python están relacionadas con asignar o almacenar valores.

a = value         ## Asignación a una variable
s[n] = value      ## Asignación a una lista
s.append(value)   ## Anexar a una lista
d['key'] = value  ## Agregar a un diccionario

Advertencia: las operaciones de asignación nunca hacen una copia del valor que se está asignando. Todas las asignaciones son simplemente copias de referencia (o copias de puntero si prefieres).

Ejemplo de asignación

Considere este fragmento de código.

a = [1,2,3]
b = a
c = [a,b]

Una representación de las operaciones de memoria subyacentes. En este ejemplo, solo hay un objeto lista [1,2,3], pero hay cuatro referencias diferentes a él.

Diagrama de referencia de memoria ejemplo

Esto significa que modificar un valor afecta a todas las referencias.

>>> a.append(999)
>>> a
[1,2,3,999]
>>> b
[1,2,3,999]
>>> c
[[1,2,3,999], [1,2,3,999]]
>>>

Observe cómo un cambio en la lista original se refleja en todos los demás lugares (ay!). Esto se debe a que nunca se hicieron copias. Todo apunta a lo mismo.

Reasignación de valores

Reasignar un valor nunca sobrescribe la memoria utilizada por el valor anterior.

a = [1,2,3]
b = a
a = [4,5,6]

print(a)      ## [4, 5, 6]
print(b)      ## [1, 2, 3]    Conserva el valor original

Recuerde: Las variables son nombres, no ubicaciones de memoria.

Algunos peligros

Si no estás al tanto de esta compartición, te causarás problemas en algún momento. Escenario típico. Modificas algunos datos pensando que es tu propia copia privada y accidentalmente corruptas algunos datos en otra parte del programa.

Comentario: Esta es una de las razones por las que los tipos de datos primitivos (int, float, string) son inmutables (de solo lectura).

Identidad y referencias

Utilice el operador is para comprobar si dos valores son exactamente el mismo objeto.

>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>>

is compara la identidad del objeto (un entero). La identidad se puede obtener utilizando id().

>>> id(a)
3588944
>>> id(b)
3588944
>>>

Nota: Casi siempre es mejor utilizar == para comprobar objetos. El comportamiento de is a menudo es inesperado:

>>> a = [1,2,3]
>>> b = a
>>> c = [1,2,3]
>>> a is b
True
>>> a is c
False
>>> a == c
True
>>>

Copias superficiales

Las listas y los diccionarios tienen métodos para copiar.

>>> a = [2,3,[100,101],4]
>>> b = list(a) ## Hacer una copia
>>> a is b
False

Es una nueva lista, pero los elementos de la lista se comparten.

>>> a[2].append(102)
>>> b[2]
[100,101,102]
>>>
>>> a[2] is b[2]
True
>>>

Por ejemplo, la lista interna [100, 101, 102] se está compartiendo. Esto se conoce como una copia superficial. Aquí está una representación.

Copia superficial

Copias profundas

A veces es necesario hacer una copia de un objeto y de todos los objetos que contiene. Puedes utilizar el módulo copy para esto:

>>> a = [2,3,[100,101],4]
>>> import copy
>>> b = copy.deepcopy(a)
>>> a[2].append(102)
>>> b[2]
[100,101]
>>> a[2] is b[2]
False
>>>

Nombres, valores, tipos

Los nombres de variables no tienen un tipo. Es solo un nombre. Sin embargo, los valores tienen un tipo subyacente.

>>> a = 42
>>> b = 'Hello World'
>>> type(a)
<type 'int'>
>>> type(b)
<type 'str'>

type() te dirá de qué tipo es. El nombre del tipo se suele utilizar como una función que crea o convierte un valor en ese tipo.

Comprobación de tipos

Cómo saber si un objeto es de un tipo específico.

if isinstance(a, list):
    print('a es una lista')

Comprobar si es uno de muchos tipos posibles.

if isinstance(a, (list,tuple)):
    print('a es una lista o una tupla')

*Precaución: No excedas en la comprobación de tipos. Puede llevar a una complejidad excesiva de código. Por lo general, solo la harías si al hacerlo se evitaran errores comunes cometidos por otros al usar tu código.

Todo es un objeto

Números, cadenas, listas, funciones, excepciones, clases, instancias, etc. son todos objetos. Esto significa que todos los objetos que se pueden nombrar se pueden pasar como datos, colocar en contenedores, etc., sin ninguna restricción. No hay especiales tipos de objetos. A veces se dice que todos los objetos son "de primer clase".

Un ejemplo simple:

>>> import math
>>> items = [abs, math, ValueError ]
>>> items
[<built-in function abs>,
  <module'math' (builtin)>,
  <type 'exceptions.ValueError'>]
>>> items[0](-45)
45
>>> items[1].sqrt(2)
1.4142135623730951
>>> try:
        x = int('not a number')
    except items[2]:
        print('Failed!')
Failed!
>>>

Aquí, items es una lista que contiene una función, un módulo y una excepción. Puedes usar directamente los elementos de la lista en lugar de los nombres originales:

items[0](-45)       ## abs
items[1].sqrt(2)    ## math
except items[2]:    ## ValueError

Con gran poder viene gran responsabilidad. Simplemente porque puedas hacer eso no significa que debas.

En este conjunto de ejercicios, examinamos algunos de los poderes que derivan de los objetos de primer clase.

Ejercicio 2.24: Datos de primer clase

En el archivo portfolio.csv, leemos datos organizados en columnas que se ven así:

name,shares,price
"AA",100,32.20
"IBM",50,91.10
...

En el código anterior, usamos el módulo csv para leer el archivo, pero todavía tuvimos que realizar conversiones de tipo manuales. Por ejemplo:

for row in rows:
    name   = row[0]
    shares = int(row[1])
    price  = float(row[2])

Este tipo de conversión también se puede realizar de manera más inteligente usando algunas operaciones básicas de listas.

Crea una lista de Python que contenga los nombres de las funciones de conversión que usarías para convertir cada columna en el tipo adecuado:

>>> types = [str, int, float]
>>>

La razón por la que puedes incluso crear esta lista es que todo en Python es de primer clase. Entonces, si quieres tener una lista de funciones, está bien. Los elementos de la lista que creaste son funciones para convertir un valor x en un tipo dado (por ejemplo, str(x), int(x), float(x)).

Ahora, lee una fila de datos del archivo anterior:

>>> import csv
>>> f = open('portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>>

Como se mencionó, esta fila no es suficiente para hacer cálculos porque los tipos son incorrectos. Por ejemplo:

>>> row[1] * row[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type'str'
>>>

Sin embargo, quizás los datos se pueden emparejar con los tipos que especificaste en types. Por ejemplo:

>>> types[1]
<type 'int'>
>>> row[1]
'100'
>>>

Intenta convertir uno de los valores:

>>> types[1](row[1])     ## Lo mismo que int(row[1])
100
>>>

Intenta convertir un valor diferente:

>>> types[2](row[2])     ## Lo mismo que float(row[2])
32.2
>>>

Intenta el cálculo con los valores convertidos:

>>> types[1](row[1])*types[2](row[2])
3220.0000000000005
>>>

Empareja los tipos de columna con los campos y mira el resultado:

>>> r = list(zip(types, row))
>>> r
[(<type'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]
>>>

Notarás que esto ha emparejado una conversión de tipo con un valor. Por ejemplo, int se empareja con el valor '100'.

La lista emparejada es útil si quieres realizar conversiones en todos los valores, uno después del otro. Prueba esto:

>>> converted = []
>>> for func, val in zip(types, row):
          converted.append(func(val))
...
>>> converted
['AA', 100, 32.2]
>>> converted[1] * converted[2]
3220.0000000000005
>>>

Asegúrate de entender lo que está sucediendo en el código anterior. En el bucle, la variable func es una de las funciones de conversión de tipo (por ejemplo, str, int, etc.) y la variable val es uno de los valores como 'AA', '100'. La expresión func(val) está convirtiendo un valor (parecido a un casteo de tipo).

El código anterior se puede comprimir en una sola comprensión de lista.

>>> converted = [func(val) for func, val in zip(types, row)]
>>> converted
['AA', 100, 32.2]
>>>

Ejercicio 2.25: Creando diccionarios

Recuerda cómo la función dict() puede crear fácilmente un diccionario si tienes una secuencia de nombres de claves y valores? Vamos a crear un diccionario a partir de los encabezados de columna:

>>> headers
['name','shares', 'price']
>>> converted
['AA', 100, 32.2]
>>> dict(zip(headers, converted))
{'price': 32.2, 'name': 'AA','shares': 100}
>>>

Por supuesto, si estás versado en comprensiones de listas, puedes hacer toda la conversión en un solo paso usando una comprensión de diccionario:

>>> { name: func(val) for name, func, val in zip(headers, types, row) }
{'price': 32.2, 'name': 'AA','shares': 100}
>>>

Ejercicio 2.26: El panorama general

Utilizando las técnicas de este ejercicio, podrías escribir declaraciones que con facilidad conviertan los campos de casi cualquier archivo de datos orientado a columnas en un diccionario de Python.

Solo para ilustrar, supongamos que lees datos de un archivo de datos diferente de esta manera:

>>> f = open('dowstocks.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> headers
['name', 'price', 'date', 'time', 'change', 'open', 'high', 'low', 'volume']
>>> row
['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '39.67', '39.69', '39.45', '181800']
>>>

Vamos a convertir los campos usando un truco similar:

>>> types = [str, float, str, str, float, float, float, float, int]
>>> converted = [func(val) for func, val in zip(types, row)]
>>> record = dict(zip(headers, converted))
>>> record
{'volume': 181800, 'name': 'AA', 'price': 39.48, 'high': 39.69,
'low': 39.45, 'time': '9:36am', 'date': '6/11/2007', 'open': 39.67,
'change': -0.18}
>>> record['name']
'AA'
>>> record['price']
39.48
>>>

Bono: ¿Cómo modificarias este ejemplo para analizar adicionalmente la entrada date en una tupla como (6, 11, 2007)?

Tómate un tiempo para reflexionar sobre lo que has hecho en este ejercicio. Volveremos a estas ideas un poco más tarde.

Resumen

¡Felicitaciones! Has completado el laboratorio de Objetos. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.