2012-05-22 6 views
5

Para un proyecto que estoy trabajando, estoy poniendo en práctica una estructura de datos de lista enlazada, que se basa en la idea de una pareja, que defino como:¿Cómo funciona la llamada en Python?

class Pair: 
    def __init__(self, name, prefs, score): 
     self.name = name 
     self.score = score 
     self.preferences = prefs 
     self.next_pair = 0 
     self.prev_pair = 0 

donde self.next_pair y son self.prev_pair punteros a los enlaces anterior y siguiente, respectivamente.

Para configurar la lista enlazada, tengo una función de instalación que se ve así.

def install(i, pair): 
    flag = 0 
    try: 
     old_pair = pair_array[i] 
     while old_pair.next_pair != 0: 
      if old_pair == pair: 
       #if pair in remainders: remainders.remove(pair) 
       return 0 
      if old_pair.score < pair.score: 
       flag = 1 
       if old_pair.prev_pair == 0: # we are at the beginning 
        old_pair.prev_pair = pair 
        pair.next_pair = old_pair 
        pair_array[i] = pair 
        break 
       else: # we are not at the beginning 
        pair.prev_pair = old_pair.prev_pair 
        pair.next_pair = old_pair 
        old_pair.prev_pair = pair 
        pair.prev_pair.next_pair = pair 
        break 
      else: 
       old_pair = old_pair.next_pair 
     if flag==0: 
      if old_pair == pair: 
       #if pair in remainders: remainders.remove(pair) 
       return 0 
      if old_pair.score < pair.score: 
       if old_pair.prev_pair==0: 
        old_pair.prev_pair = pair 
        pair.next_pair = old_pair 
        pair_array[i] = pair 
       else: 
        pair.prev_pair = old_pair.prev_pair 
        pair.next_pair = old_pair 
        old_pair.prev_pair = pair 
        pair.prev_pair.next_pair = pair 
      else: 
       old_pair.next_pair = pair 
       pair.prev_pair = old_pair 
     except KeyError: 
      pair_array[i] = pair 
      pair.prev_pair = 0 
      pair.next_pair = 0 

En el transcurso del programa, estoy construyendo un diccionario de estas listas enlazadas, y tomando enlaces fuera de algunos y la adición de ellos en otros. Entre ser recortado y reinstalado, los enlaces se almacenan en una matriz intermedia.

En el transcurso de la depuración de este programa, me he dado cuenta de que mi comprensión de la forma en que Python pasa los argumentos a las funciones es errónea. Considere este caso de prueba que escribí:

def test_install(): 
    p = Pair(20000, [3, 1, 2, 50], 45) 
    print p.next_pair 
    print p.prev_pair 
    parse_and_get(g) 
    first_run() 
    rat = len(juggler_array)/len(circuit_array) 
    pref_size = get_pref_size() 
    print pref_size 
    print install(3, p) 
    print p.next_pair.name 
    print p.prev_pair    

Cuando ejecuto esta prueba, obtengo el siguiente resultado.

0 
0 
10 
None 
10108 
0 

Lo que no entiendo es por qué la segunda llamada a p.next_pair produce un resultado diferente (10108) que la primera llamada (0). install no devuelve un objeto Pair que pueda sobrescribir el que se entregó (devuelve None), y no es como si estuviera pasando install un puntero.

Mi comprensión de la llamada por valor es que el intérprete copia los valores pasados ​​en una función, sin modificar las variables de la persona que llama. Por ejemplo, si digo

def foo(x): 
    x = x+1 
    return x 

baz = 2 
y = foo(baz) 
print y 
print baz 

Entonces 3 y 2 se imprimirán, respectivamente. Y de hecho, cuando lo pruebo en el intérprete de Python, eso es lo que sucede.

Realmente agradecería que alguien me señale la dirección correcta aquí.

+3

Python no tiene call-by-value. Tampoco tiene llamada por referencia. –

+0

Para su información, puede usar el botón '{}' en la parte superior del cuadro de edición para sangrar un bloque de código seleccionado en 4 espacios: es una manera fácil de mantener la sangría correcta. – senderle

+2

Esa clase 'Pair' parece que probablemente debería ser un' dict'. – Daenyth

Respuesta

8

En Python, todo es un objeto. La asignación simple almacena una referencia en el objeto asignado en el nombre asignado. Como resultado, es más sencillo pensar en las variables de Python como nombres que están asignados a objetos, en lugar de objetos que están almacenados en ubicaciones con nombre.

Por ejemplo:

baz = 2 

... tiendas en baz un puntero, o de referencia, al objeto entero 2 que se almacena en otro lugar. (Dado que el tipo int es inmutable, Python en realidad tiene un grupo de enteros pequeños y reutiliza el mismo objeto 2 en todas partes, pero este es un detalle de implementación que no nos debe preocupar demasiado.)

Cuando se llama a foo(baz), foo() 's variable local x también apunta al objeto entero 2 al principio. Es decir, el foo() -nombre local x y el nombre global baz son nombres para el mismo objeto, 2. Luego se ejecuta x = x + 1. Esto cambia x para apuntar a un objeto diferente: 3.

Es importante entender: x no es una caja que contiene 2 y 2 se incrementa entonces a 3. No, x inicialmente apunta a 2 y ese puntero se cambia para apuntar a 3. Naturalmente, como no hemos cambiado a qué objeto hace referencia el punto baz, todavía apunta a 2.

Otra forma de explicarlo es que en Python, toda aprobación de argumentos es por valor, pero todos los valores son referencias a objetos.

Un resultado contrario a la intuición de esto es que si un objeto es mutable, se puede modificar mediante cualquier referencia y todas las referencias "verán" el cambio. Por ejemplo, considere esto:

baz = [1, 2, 3] 

def foo(x): 
    x[0] = x[0] + 1 

foo(baz) 
print baz 
>>> [2, 2, 3] 

Este parece muy diferente de nuestro primer ejemplo. Pero en realidad, el argumento se pasa de la misma manera. foo() recibe un puntero a baz bajo el nombre x y luego realiza una operación en él que lo cambia (en este caso, el primer elemento de la lista apunta a un objeto diferente int). La diferencia es que el nombre x nunca apunta a un nuevo objeto; es x[0] que se modifica para apuntar a un objeto diferente. x en sí mismo todavía apunta al mismo objeto que baz. (De hecho, debajo del capó la asignación a x[0] se convierte en una llamada a método: x.__setitem__()). Por lo tanto, baz "ve" la modificación de la lista. ¿Cómo no podría?

No ve este comportamiento con enteros y cadenas porque no puede cambiar enteros o cadenas; son tipos inmutables, y cuando los modifica (por ejemplo, x = x + 1) no los está modificando en realidad, sino que vincula su nombre de variable a un objeto completamente diferente. Si cambia baz por una tupla, p. baz = (1, 2, 3), encontrará que foo() le da un error porque no puede asignar elementos de una tupla; las tuplas son otro tipo inmutable. "Cambiar" una tupla requiere crear una nueva, y la asignación apunta la variable al nuevo objeto.

Los objetos de las clases que defina son mutables, por lo que su instancia Pair se puede modificar mediante cualquier función en la que se pase; es decir, los atributos se pueden agregar, eliminar o reasignar a otros objetos. Ninguna de estas cosas volverá a enlazar ninguno de los nombres que apuntan a su objeto, por lo que todos los nombres que actualmente lo señalan "verán" los cambios.

+0

.. y la respuesta completa es _superb_. ¡Gracias! – sarnold

+0

Excelente respuesta. Este es el mejor que he visto explicado. –

1

voy a lanzar en un factor que complica un poco:

>>> def foo(x): 
... x *= 2 
... return x 
... 

definir una función ligeramente diferente utilizando un método sé que se propone para los números, listas y cadenas.

En primer lugar, llamar con cadenas:

>>> baz = "hello" 
>>> y = foo(baz) 
>>> y 
'hellohello' 
>>> baz 
'hello' 

A continuación, llamarlo con listas:

>>> baz=[1,2,2] 
>>> y = foo(baz) 
>>> y 
[1, 2, 2, 1, 2, 2] 
>>> baz 
[1, 2, 2, 1, 2, 2] 
>>> 

Con cuerdas, el argumento no es modificado. Con listas, el argumento se modifica.

Si fuera yo, evitaría modificar argumentos dentro de los métodos.

+3

Bueno, evite usar operadores in situ en argumentos que podrían ser tipos mutables, a menos que intente mutarlos. – kindall

+0

@Kindall: Eso es mucho más elegante y preciso. Bonito. – sarnold

8

Python no copia nada al pasar variables a una función. No es call-by-value ni call-by-reference, pero de esos dos es más similar a call-by-reference.Podría pensarlo como "llamar por valor, pero el valor es una referencia".

Si pasa un objeto mutable a una función, la modificación del objeto dentro de la función afectará al objeto donde aparezca. (Si pasa un objeto inmutable a una función, como una cadena o un número entero, entonces, por definición, no puede modificar el objeto)

La razón por la cual esto no es técnicamente pasado por referencia es que puedes volver a enlazar un nombre para que el nombre se refiera a algo completamente distinto. (Para los nombres de objetos inmutables, esto es lo único que puede hacer con ellos). Volver a vincular un nombre que existe solo dentro de una función no afecta a ningún nombre que pueda existir fuera de la función.

En su primer ejemplo con los objetos Pair, está modificando un objeto, por lo que puede ver los efectos fuera de la función.

En su segundo ejemplo, no está modificando ningún objeto, solo está volviendo a enlazar nombres a otros objetos (otros enteros en este caso). baz es un nombre que apunta a un objeto entero (en Python, todo es un objeto, incluso enteros) con un valor de 2. Cuando pasa baz a foo(x), se crea el nombre x localmente dentro de la función foo en la pila, y x se establece en el puntero que se pasó a la función, el mismo puntero que baz. Pero x y baz no son lo mismo, solo contienen punteros al mismo objeto. En la línea x = x+1, x se recupera para apuntar a un objeto entero con un valor de 3, y ese puntero es lo que se devuelve de la función y se utiliza para vincular el objeto entero a y.

Si ha reescrito su primer ejemplo para crear explícitamente un nuevo objeto Pair dentro de su función en función de la información del objeto Pair pasado (si se trata de una copia que luego modifica, o si crea un constructor que modifica el datos sobre la construcción) entonces su función no tendría el efecto secundario de modificar el objeto que se pasó.

Editar: Por cierto, en Python no debe usar 0 como marcador de posición para significar "No hago" t tiene un valor "- use None. Y del mismo modo, no debe usar 0 para indicar False, como parece estar haciendo en flag. Pero todos 0, None y False se evalúan como False en expresiones booleanas, por lo que no importa cuál de las que use, puede decir cosas como if not flag en lugar de if flag == 0.

+0

Gracias por la respuesta reflexiva, creo que tengo una mejor idea de lo que sucede cuando Python evalúa un procedimiento. En cuanto a su edición final, ¿por qué usar None se considera más Pythonic? ¿Es solo que es más legible, o podría haber un error que podría resultar de usar 0 en su lugar? –

+0

Simplemente porque es más legible. No hay un problema obvio con el uso de 0 para None a menos que pueda confundirse con un valor válido (por ejemplo, si alguna vez almacena un entero en ese punto o intenta hacer cálculos matemáticos con él). –

2

Le sugiero que se olvide de implementar una lista vinculada, y simplemente use una instancia de Python list. Si necesita algo diferente al predeterminado Python list, tal vez pueda usar algo de un módulo de Python como collections.

Un ciclo de Python para seguir los enlaces de una lista vinculada se ejecutará a la velocidad del intérprete de Python, es decir, lentamente.Si simplemente usa la clase incorporada list, las operaciones de su lista ocurrirán en el código C de Python, y obtendrá velocidad.

Si necesita algo así como una lista pero con una inserción rápida y una eliminación rápida, ¿puede hacer que un dict funcione? Si hay algún tipo de valor de ID (cadena o entero o lo que sea) que se puede usar para imponer un orden en sus valores, puede usarlo como un valor de clave y obtener un rayo rápido insertar y eliminar valores. Luego, si necesita extraer los valores en orden, puede usar la función de método dict.keys() para obtener una lista de valores clave y usar eso.

Pero si realmente necesita listas vinculadas, le sugiero que encuentre el código escrito y depurado por otra persona, y lo adapte a sus necesidades. Búsqueda en Google de "receta de lista enlazada de Python" o "módulo de lista enlazada de Python".

Cuestiones relacionadas