36

En algunas aplicaciones sencillas que me ha tocado escribir en C/C++ he visto la facilidad con la que se resuelven ciertas tareas utilizando punteros. Ahora, más interesado en otro lenguaje: Python, he notado la ausencia de este concepto. ¿A qué se debe esta ausencia? Siendo Python un lenguaje muy poderoso y utilizado, entonces, ¿qué concepto lo sustituye?, ¿está implícito en los tipos de datos, en las asignaciones, en la instanciación de una clase?

Un ejemplo extremadamente sencillo sería que en C podemos codificar cosas como esta:

#include <stdio.h>

int main(void) {
    // your code goes here
    int a = 5;
    int *b = &a;
    printf("a = %d; b = %d\n", a, *b); // (1)

    a = 6;
    printf("a = %d; b = %d\n", a, *b); // (2)
    return 0;
}

(1): a = 5; b = 5

(2): a = 6; b = 6

b apunta a la dirección de memoria de a, cualquier modificación en a podrá ser observada al desreferenciar b. Cualquier asignación por indirección *b = <valor>; modificará a.

Pero, en Python:

a = 5
b = a
print "a = {0:d}; b = {1:d}".format(a,b) # (1)
b is a # resultado: True

a = 6
print "a = {0:d}; b = {1:d}".format(a,b) # (2)
b is a # resultado: False

(1): a = 5; b = 5

(2): a = 6; b = 5

Al principio a y b hacen referencia al mismo objeto. Luego, cuando a es modificado se crea un nuevo objeto; entonces, ambos hacen referencia a objetos diferentes y de valores diferentes.

No hay una forma de hacer lo que en C en Python con este tipo de dato, pero es posible hacer algo similar con tipos de datos mutables; sin embargo, solo se puede cuando hacemos modificaciones internas del dato mutable, por ejemplo: cambiar el valor de un elemento de una lista.

a = [1, 2]
b = a

print b is a # resultado: True

b.append(3)

print b is a # resultado: True

a = [4, 5]

print b is a # resultado: False
osjerick
  • 959
  • 1
  • 9
  • 21
  • Estaría bueno que comentes algún caso puntual en que los punteros te parezca necesario el concepto para hacer la pregunta más clara (y también las posibles respuestas) – eloyesp Dec 04 '15 at 00:05
  • esto vendría a ser un alias no? dos nombres para una misma variable, me parece que usar esto va a hacer que el código sea más difícil de entender ya que al modificar un valor hace cambiar otra variable que nunca se modificó. me gustaría ver un caso de uso real para esta técnica. – Samus_ Dec 05 '15 at 23:17
  • 2
    El concepto de variable al estilo C (posición de memoria cuyo contenido cambia) no tiene sentido en Python. Lo que se conoce como variables en realidad son 'nombres' que guardan una referencia a un objeto (una instancia de un tipo), esto en sí es un puntero expresado de forma implícita. El operador _is_ compara las referencias de ambos nombres y devuelve True en caso de que sean iguales, esto es, que apunten al mismo objeto. – tinproject Dec 06 '15 at 21:02

4 Answers4

41

Su ausencia se debe a que el uso explícito de punteros es una característica de lenguajes de más bajo nivel como el C. Lenguajes de alto nivel como Python lo evitan con el propósito de hacer más fácil y ágil su utilización, así como no tener que conocer detalles del modelo de datos.

Que el programador de Python no tenga que lidiar con los punteros no quiere decir que el intérprete no haga uso de ellos. De hecho los usa profusamente de forma implícita.

En Python todo es un objeto creado en la memoria dinámica (mantenida automáticamente). Cuando llamas a una función los argumentos son pasados mediante sus punteros. Es lo que se conoce como convención de llamada por objeto. De igual forma si asignas a = b, a lo que guarda es el puntero del objeto de b. Así que todas las variables son punteros a objetos, que son manejados implícitamente.

Lo que sí hay que diferenciar es entre objetos inmutables y mutables.

  • Inmutables son los números, las cadenas o las tuplas. Al asignar x = 2015 creará un objeto entero y x apuntará a él pero el contenido de ese objeto no podrá ser modificado. Si luego asignas x = 2016 lo que hará internamente será crear un nuevo objeto con el nuevo contenido.
  • Mutables son otros objetos como los diccionarios o las listas. En este caso dichos objetos sí podrán ser modificados. Si tienes v = [1] y luego llamas a v.append(2), v seguirá apuntando al mismo objeto, pero su contenido habrá cambiado.

En resumen, al ejecutar este código:

x = 2015
y = x
x = 2016

print x
print y

v = [1]
w = v
v.append(2)

print v
print w

El resultado será:

2016
2015
[1, 2]
[1, 2]
Guillermo Ruiz
  • 816
  • 7
  • 4
6

La respuesta de @GuillermoRuiz me parece excelente, pero querría profundizar en algunos detalles sobre la mutabilidad e inmutabilidad, que suelen confundir al principio, pero que están muy claros si se tiene presente que todo son punteros.

Cambiar elementos de una lista

El hecho de que una lista sea "mutable" implica no sólo que podemos añadir elementos a ella, sino que podemos cambiar los valores guardados.

En realidad, siendo puristas, la lista lo único que contiene son "punteros" a los datos en cuestión. Es decir, una lista como esta:

a = [1, 2, 3]

Contiene en realidad tres punteros, cada uno apuntando a un entero, de valores respectivos 1, 2 y 3. Si ahora cambiamos el primer elemento:

a[0] = 100

Al imprimir la lista veremos:

>>> a
[100, 2, 3]

Esto no quiere decir que el primer elemento de la lista haya sido sustituido por un 100 (eso sería cierto en un array C), sino que se ha creado un nuevo objeto de tipo entero y que el puntero que había en el primer elemento de la lista que apuntaba a un 1, ahora apunta al 100. El 1 previo queda "sin referencias" y será eliminado de memoria más adelante por el recolector de basura.

Copias de listas

Por otro lado, el hecho de que las variables sean en realidad punteros (o si prefieres, referencias) implica que la siguiente asignación:

b = a

no copia los elementos de a, sino que simplemente asigna a b una copia del puntero. a. Es decir, a y b apuntan en realidad a la misma lista. Por tanto si hacemos:

b[0] = 50

es lo mismo que si hubiéramos hecho a[0] = 50.

Para comprobar si dos variables "apuntan" a un mismo dato, Python ofrece el comparador is:

>>> a is b
True

Si no queremos que apunten al mismo, sino que sean una copia (en lugares diferentes de la memoria), podemos lograrlo así:

b = list(a)

o así:

b = a[:]

En cualquiera de estos dos casos:

>>> a is b
False
>>> a == b
True

El operador == compara los elementos de a con los de b, mientras que is compara la identidad de a con la de b, lo que en la práctica consiste en comparar a qué dirección de memoria se refiere cada uno.

Listas como parámetros

Lo anterior explica también por qué una función puede modificar elementos de una lista que reciba como parámetro:

def pon_cero(lista):
   lista[0] = 0

a = [1,2,3]
pon_cero(a)
print(a)
# [0, 2, 3]

En una tupla, al ser inmutable, esto no puede hacerse.

Inmutabilidad, pero no tanto

Pero cuidado, el que una tupla sea inmutable sólo significa que no se puede cambiar el valor de tupla[i] por otro valor, pero si tupla[i] fuese una lista, los valores de esa lista sí podrían cambiarse:

tupla = ( [0,0], [1,1] )
# tupla[0] = [2,2] # No está permitido. Error
tupla[0][0] = 2
tupla[0][1] = 2    # No hay problema aqui en cambio
print(tupla)
([2,2], [1,1])
abulafia
  • 53,696
  • 3
  • 45
  • 80
6

En C, los punteros suelen satisfacer tres necesidades: referenciar estructuras reservadas dinámicamente, pasar parámetros a una función por referencia, o iterar una colección.

En el caso de Python, y los lenguajes de objetos con memoria automática en general, las variables cumplen la función de referenciar estructuras creadas dinámicamente: uno puede crear instancias de los objetos en cualquier momento.

En general, los objetos de reservan en la memoria dinámica de los procesos, y las variables son referencias a ellos: casi casi que las referencias son abstracciones de los punteros, con algunas propiedades más.

Por este motivo, el pasaje de parámetros se hace siempre por referencia, por lo que no se necesitan punteros para esto.

Por último, en los lenguajes de objetos existen objetos iteradores, que exponen una interfaz de más alto nivel para recorrer colecciones de datos.

Abstraerse de los detalles de la memoria del proceso es algo buscado en los lenguajes, y por todo esto es que no son necesarios los punteros: por diseño.

mgarciaisaia
  • 633
  • 5
  • 14
  • 3
    *Por este motivo, el pasaje de parámetros se hace siempre por referencia, por lo que no se necesitan punteros para esto.* **falso**, el paso de parámetros se hace por valor, pero se trata de valores referencia. Ni Python, ni Java, ni JavaScript, ni muchos otros permiten modificar el valor concreto de una variable pasada por parámetro, solo *el valor al que hacen referencia*, y solo para tipos mutables. – Darkhogg Dec 04 '15 at 01:04
0

Es un poco chapuza, pero si realmente necesitas 1 variable y un "puntero" puedes hacer:

class mutable_obj:
    def __init__(self, x):
        self.x = x

a = mutable_obj(10)
b = a
a.x == b.x # True
a.x = 30
a.x == b.x # True
b.x = 86
a.x == b.x # True

Creo que es innecesario, igualmente si alguien sabe una mejor manera ¡que lo diga! :D

Pablo
  • 43
  • 5