2012-09-26 15 views
5

Tengo una estructura de datos Python compleja (si es importante, es un gran objeto Score21 music) que no saldrá debido a la presencia de un punto débil en algún lugar dentro de la estructura del objeto. Ya he depurado estos problemas con el depurador de pila y el depurador de Python, pero siempre es un gran problema. ¿Hay alguna herramienta que ejecute dir() de forma recursiva en todos los atributos de un objeto, encuentre objetos ocultos en listas, tuplas, dictos, etc., y devuelva aquellos que coinciden con un determinado valor (una función lambda o algo así). Un gran problema son las referencias recursivas, por lo que se necesita algún tipo de función memo (como copy.epcopy). Probé:Recursivamente dir() un objeto python para encontrar valores de un cierto tipo o con un cierto valor

import weakref 
def findWeakRef(streamObj, memo=None): 
    weakRefList = [] 
    if memo is None: 
     memo = {} 
    for x in dir(streamObj): 
     xValue = getattr(streamObj, x) 
     if id(xValue) in memo: 
      continue 
     else: 
      memo[id(xValue)] = True 
     if type(xValue) is weakref.ref: 
      weakRefList.append(x, xValue, streamObj) 
     if hasattr(xValue, "__iter__"): 
      for i in xValue: 
       if id(i) in memo: 
        pass 
       else: 
        memo[id(i)] = True 
        weakRefList.extend(findWeakRef(i), memo) 
     else: 
      weakRefList.extend(findWeakRef(xValue), memo) 
    return weakRefList 

es probable que pueda continuar tapar agujeros en esto (el iter no es lo que yo quiero para predice, por ejemplo), pero antes me tiro más tiempo en él, preguntándose si alguien sabe una respuesta más fácil. Podría ser una herramienta general bastante útil.

+1

Todavía no he visto una solución preparada. Quizás gc.get_referents en lugar de dir te lleve un poco más lejos. – Pankrat

+0

Corta el cruft (__eq__, etc.) aunque a un precio que el formato de lo que se devuelve cambia por el tipo de objeto, por lo que probablemente sea una solución más rápida, pero no más simple. Tampoco parece ser compatible con la recursividad. ¡Gracias! –

+1

¿Ha considerado la subclasificación de la clase 'pickle.Pickler'? La fuente está incluida en '.../Lib/pickle.py'. Hacerlo debería permitirle reutilizar una gran cantidad de código y atrapar 'PickleError's para hacer lo que usted describe, así como recuperar el protocolo de decapado bien establecido que Python ya tiene en su lugar. – martineau

Respuesta

2

Parece ser el comienzo de una respuesta. Tuve que respaldar algunos elementos de Python 3.2 inspect.getattr_static para que funcionase, por lo que no llamó a las propiedades que seguían generando nuevos objetos. Aquí está el código que se me ocurrió:

#------------------------------------------------------------------------------- 
# Name:   treeYield.py 
# Purpose:  traverse a complex datastructure and yield elements 
#    that fit a given criteria 
# 
# Authors:  Michael Scott Cuthbert 
# 
# Copyright: Copyright © 2012 Michael Scott Cuthbert 
# License:  CC-BY 
#------------------------------------------------------------------------------- 
import types 

class TreeYielder(object): 
    def __init__(self, yieldValue = None): 
     ''' 
     `yieldValue` should be a lambda function that 
     returns True/False or a function/method call that 
     will be passed the value of a current attribute 
     '''   
     self.currentStack = [] 
     self.yieldValue = yieldValue 
     self.stackVals = [] 
     t = types 
     self.nonIterables = [t.IntType, t.StringType, t.UnicodeType, t.LongType, 
          t.FloatType, t.NoneType, t.BooleanType] 

    def run(self, obj, memo = None): 
     ''' 
     traverse all attributes of an object looking 
     for subObjects that meet a certain criteria. 
     yield them. 

     `memo` is a dictionary to keep track of objects 
     that have already been seen 

     The original object is added to the memo and 
     also checked for yieldValue 
     ''' 
     if memo is None: 
      memo = {} 
     self.memo = memo 
     if id(obj) in self.memo: 
      self.memo[id(obj)] += 1 
      return 
     else: 
      self.memo[id(obj)] = 1 

     if self.yieldValue(obj) is True: 
      yield obj 


     ### now check for sub values... 
     self.currentStack.append(obj) 

     tObj = type(obj) 
     if tObj in self.nonIterables: 
      pass 
     elif tObj == types.DictType: 
      for keyX in obj: 
       dictTuple = ('dict', keyX) 
       self.stackVals.append(dictTuple) 
       x = obj[keyX] 
       for z in self.run(x, memo=memo): 
        yield z 
       self.stackVals.pop() 

     elif tObj in [types.ListType, types.TupleType]: 
      for i,x in enumerate(obj): 
       listTuple = ('listLike', i) 
       self.stackVals.append(listTuple) 
       for z in self.run(x, memo=memo): 
        yield z 
       self.stackVals.pop() 

     else: # objects or uncaught types... 
      ### from http://bugs.python.org/file18699/static.py 
      try: 
       instance_dict = object.__getattribute__(obj, "__dict__") 
      except AttributeError: 
       ## probably uncaught static object 
       return 

      for x in instance_dict: 
       try: 
        gotValue = object.__getattribute__(obj, x) 
       except: # ?? property that relies on something else being set. 
        continue 
       objTuple = ('getattr', x) 
       self.stackVals.append(objTuple) 
       try: 
        for z in self.run(gotValue, memo=memo): 
         yield z 
       except RuntimeError: 
        raise Exception("Maximum recursion on:\n%s" % self.currentLevel()) 
       self.stackVals.pop()     

     self.currentStack.pop() 

    def currentLevel(self): 
     currentStr = "" 
     for stackType, stackValue in self.stackVals: 
      if stackType == 'dict': 
       if isinstance(stackValue, str): 
        currentStr += "['" + stackValue + "']" 
       elif isinstance(stackValue, unicode): 
        currentStr += "[u'" + stackValue + "']" 
       else: # numeric key... 
        currentStr += "[" + str(stackValue) + "]" 
      elif stackType == 'listLike': 
       currentStr += "[" + str(stackValue) + "]" 
      elif stackType == 'getattr': 
       currentStr += ".__getattribute__('" + stackValue + "')" 
      else: 
       raise Exception("Cannot get attribute of type %s" % stackType) 
     return currentStr 

Este código permite ejecutar algo como esto:

class Mock(object): 
    def __init__(self, mockThing, embedMock = True): 
     self.abby = 30 
     self.mocker = mockThing 
     self.mockList = [mockThing, mockThing, 40] 
     self.embeddedMock = None 
     if embedMock is True: 
      self.embeddedMock = Mock(mockThing, embedMock = False) 

mockType = lambda x: x.__class__.__name__ == 'Mock' 

subList = [100, 60, -2] 
myList = [5, 20, [5, 12, 17], 30, {'hello': 10, 'goodbye': 22, 'mock': Mock(subList)}, -20, Mock(subList)] 
myList.append(myList) 

ty = TreeYielder(mockType) 
for val in ty.run(myList): 
    print(val, ty.currentLevel()) 

Y conseguir:

(<__main__.Mock object at 0x01DEBD10>, "[4]['mock']") 
(<__main__.Mock object at 0x01DEF370>, "[4]['mock'].__getattribute__('embeddedMock')") 
(<__main__.Mock object at 0x01DEF390>, '[6]') 
(<__main__.Mock object at 0x01DEF3B0>, "[6].__getattribute__('embeddedMock')") 

O ejecutar:

high = lambda x: isinstance(x, (int, float)) and x > 10 
ty = TreeYielder(high) 
for val in ty.run(myList): 
    print(val, ty.currentLevel()) 

Y obtenga:

(20, '[1]') 
(12, '[2][1]') 
(17, '[2][2]') 
(30, '[3]') 
(22, "[4]['goodbye']") 
(100, "[4]['mock'].__getattribute__('embeddedMock').__getattribute__('mocker')[0]") 
(60, "[4]['mock'].__getattribute__('embeddedMock').__getattribute__('mocker')[1]") 
(40, "[4]['mock'].__getattribute__('embeddedMock').__getattribute__('mockList')[2]") 

Todavía estoy tratando de descubrir por qué .abby no se encuentra, pero creo que vale la pena publicar incluso en este punto, ya que está mucho más en el camino correcto que cuando comencé.

Cuestiones relacionadas