2010-03-09 15 views
7

Estoy considerando pasar de Matlab a Python/numpy para el análisis de datos y simulaciones numéricas. He usado Matlab (y SML-NJ) durante años, y estoy muy cómodo en el entorno funcional sin efectos secundarios (excepto E/S), pero soy un poco reacio a los efectos secundarios en Python. ¿Pueden las personas compartir sus consejos favoritos sobre los efectos secundarios, y si es posible, cómo se las arreglaron? A modo de ejemplo, yo estaba un poco sorprendido cuando probé el siguiente código en Python:gotchas de efecto secundario en python/numpy? historias de terror y escapes estrechos quería

lofls = [[]] * 4 #an accident waiting to happen! 
lofls[0].append(7) #not what I was expecting... 
print lofls   #gives [[7], [7], [7], [7]] 
#instead, I should have done this (I think) 
lofls = [[] for x in range(4)] 
lofls[0].append(7) #only appends to the first list 
print lofls   #gives [[7], [], [], []] 

gracias de antemano

+0

Esa fue la principal Gotcha para mí en cambio a Python, pero creo que también es una Gotcha común que las personas que comienzan a utilizar Python sin haber utilizado Matlab antes. Sin embargo, podría estar equivocado. –

+1

Relacionados: http://zephyrfalcon.org/labs/python_pitfalls.html http://www.ferg.org/projects/python_gotchas.html – jfs

Respuesta

9

referencias confuso para el mismo objeto (mutable) con referencias a objetos separados es de hecho un " gotcha "(padecido por todos los lenguajes no funcionales, que tienen objetos mutables y, por supuesto, referencias). Un error frecuente en el código Python para principiantes está haciendo mal uso de un valor predeterminado que es mutable, por ejemplo:

def addone(item, alist=[]): 
    alist.append(item) 
    return alist 

Este código puede ser correcto si el propósito es tener addone mantener su propio estado (y devuelve la lista de un crecimiento a las personas que llaman sucesivamente), al igual que static datos trabajarían en C; no es correcto si el codificador está asumiendo erróneamente que se hará una nueva lista vacía en cada llamada.

principiantes primas utilizadas para los lenguajes funcionales también pueden ser confundidos por la decisión command-query separation diseño en incorporadas en recipientes de Python: métodos de mutación que no tienen nada en particular para volver (es decir, la gran mayoría de mutar métodos) no regresar (específicamente, devuelven None) - están haciendo todo su trabajo "en el lugar". Los errores que provienen de malentendidos son fáciles de detectar, p.

alist = alist.append(item) 

es más o menos garantiza que sea un error - que añade un elemento a la lista mencionada por su nombre alist, pero luego vuelve a vincular el nombre alist a None (el valor de retorno de la llamada append).

Si bien el primer problema que mencioné es acerca de una vinculación temprana que puede engañar a las personas que piensan que la vinculación es tardía, hay problemas que van en la dirección opuesta, donde las expectativas de algunas personas son de una vinculación temprana mientras que el enlace es, en cambio, tarde. Por ejemplo (con un marco hipotético interfaz gráfica de usuario ...):

for i in range(10): 
    Button(text="Button #%s" % i, 
      click=lambda: say("I'm #%s!" % i)) 

esto mostrará diez botones diciendo "Botón # 0", "Botón # 1", etc., pero, al hacer clic, todos y cada uno de ellos serán say es #9 - porque el identro del lambda está atrasado (con un cierre léxico). Una solución es tomar ventaja del hecho de que los valores por defecto de argumento son-principios determinada (como he señalado sobre el primer número! -) y cambiar la última línea de

  click=lambda i=i: say("I'm #%s!" % i)) 

Ahora lambda 's i es un argumento con un valor predeterminado, no una variable libre (consultada por cierre léxico), por lo que el código funciona según lo previsto (también existen otras formas, por supuesto).

+1

el problema de los valores por defecto mutables es tan común, aparece en todos los tutoriales de Python que tengo visto, y me he asustado directamente. La regla general que enumera con respecto a la modificación "en el lugar" es buena, y creo que me llevará muy lejos. Sin embargo, el enlace tardío en la expresión lambda que me das es muy desconcertante para mí; esto es lo contrario de lo que sucede en Matlab, y no estoy seguro de que me guste. sin embargo, son los descansos. – shabbychef

+0

@shabbychef, el valor ligado a un nombre siempre se busca en el momento en que se necesita, no antes (y por supuesto no más tarde ;-). Las variables gratuitas utilizadas en las funciones (lambda u otras) que son variables locales en una función "externa" que contiene léxico no son una excepción a esta regla. El cuerpo de una función (otra vez, lambda o no) siempre se ejecuta cuando se llama a la función, no antes (y, por supuesto, no más tarde), así que claramente es cuando se necesitan los valores de las variables libres, por lo que es cuando están Miró hacia arriba. ¿Qué otro comportamiento podría ser consistente? –

+0

por extraño que parezca, el comportamiento de matlab en este sentido es lo que he llegado a conocer y tolerar; uno puede definir una función anónima (esencialmente un 'lambda'), que usa variables del alcance externo, y están vinculadas al valor cuando se creó la función anónima. de hecho, uno puede guardar la función anónima para archivar (esencialmente decapado), cerrar matlab, apagar la computadora, volver, reiniciar, recargar, y la función anónima aún funciona, todavía tiene variables de su contexto. Creo que Mathworks tuvo que hacer esto porque sus funciones anónimas son tan limitadas en su utilidad de otra manera ... – shabbychef

0

Me encontré con este recientemente otra vez, (después de años de pitón) al tratar de eliminar una pequeña dependencia de numpy.

Si proviene de matlab, debe utilizar y confiar en las funciones numpy para el manejo de matriz de tipo mono. Junto con matplotlib, son algunos paquetes muy convenientes para una transición sin problemas.

import numpy as np 
np.zeros((4,)) # to make an array full of zeros [0,0,0,0] 
np.zeros((4,1)) # another one full of zeros but 2 dimensions [[0],[0],[0],[0]] 
np.zeros((4,0)) # an empty array like [[],[],[],[]] 
np.zeros((0,4)) # another empty array, which can not be represented with python lists o_O 

etc.

Cuestiones relacionadas