Alcance del gotcha cuando se agregan dinámicamente métodos en un bucle.

Tengo una API para analizar los datos de mi ejercicio (que raspo del sitio web del runkeeper ).

Mi clase principal es una subclase de un pandas.DataFrame , que es básicamente un contenedor para datos tabulares. Admite la indexación por nombre de columna, devolviendo una matriz de los valores de columna.

Me gustaría agregar algunas propiedades de conveniencia basadas en los tipos de “actividades físicas” que están presentes en los datos. Entonces, por ejemplo, me gustaría agregar una propiedad ‘corriendo’:

 @property def running(self): return self[self['type'] == 'running'] 

Lo que devolvería todas las filas del DataFrame que tienen ‘corriendo’ en la columna ‘tipo’.

Intenté hacer esto dinámicamente para todos los tipos presentes en los datos. Esto es lo que ingenuamente hice:

 class Activities(pandas.DataFrame): def __init__(self,data): pandas.DataFrame.__init__(self,data) # The set of unique types in the 'type' column: types = set(self['type']) for type in types: method = property(lambda self: self[self['type'] == type]) setattr(self.__class__,type,method) 

El resultado fue que todas estas propiedades terminaron devolviendo tablas de datos para el mismo tipo de actividad (“caminar”).

Lo que está sucediendo es que cuando se accede a las propiedades, se llama a los lambdas y se ven en el ámbito en el que se definieron para el nombre ‘tipo’. Ellos encuentran que está enlazado a la cadena ‘walking’, ya que esa fue la última iteración del bucle for. Cada iteración del bucle for no tiene su propio espacio de nombres, por lo que todas las lambdas solo ven la última iteración, en lugar del valor que “tipo” tenía cuando se definieron realmente.

¿Alguien puede hacer algo para evitar esto? Puedo pensar en dos, pero no parecen particularmente ideales:

  1. defina __getattr__ para verificar que el atributo sea un tipo de actividad y devuelva las filas apropiadas.

  2. use una llamada a función recursiva en lugar de un bucle for, de modo que cada nivel de recursión tenga su propio espacio de nombres.

Ambos son un poco demasiado inteligentes para mis gustos, y pandas.DataFrame ya tiene un __getattr__ que tendría que interactuar con cautela si hiciera uno también. Y la recursión funcionaría, pero se siente muy mal ya que el conjunto de tipos no tiene ninguna estructura intrínseca en forma de árbol. Es plano, y debe verse plano en el código!

Modifique la lambda para atraer los valores al nuevo ámbito.

 method = property(lambda self=self, type=type: self[self['type'] == type]) 

Yo sugeriría no hacer una subclase de DataFrame, honestamente, si puedes evitarlo. El viejo adagio de Java “favorecer la composición sobre la herencia” tiende a ser preferible en mi experiencia.