2012-09-26 30 views
5

Mam złożoną strukturę danych Pythona (jeśli to ważne, to duży obiekt Music21 Score), który nie będzie się dusił ze względu na obecność weakref gdzieś głęboko w strukturze obiektu. Już wcześniej debugowałem takie problemy ze śledzeniem stosu i debugerem Pythona, ale zawsze jest to duży problem. Czy istnieje narzędzie, które uruchamia dir() rekursywnie na wszystkich atrybutach obiektu, znajdowanie obiektów ukrytych na listach, krotkach, dyktach itp. I zwraca te, które pasują do określonej wartości (funkcja lambda lub coś w tym stylu). Dużym problemem są referencje rekursywne, więc potrzebna jest jakaś funkcja memo (jak na przykład copy.deepcopy). Próbowałem:Rekurencyjnie dir() obiekt Pythona, aby znaleźć wartości pewnego typu lub o pewnej wartości

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 

mogę prawdopodobnie nadal zatykania dziur w to (the iter nie jest to, czego chcę dla dicts, na przykład), ale zanim rzucę jeszcze raz do niego, zastanawiając się, czy ktoś zna łatwiejszą odpowiedź. Może to być całkiem przydatne ogólne narzędzie.

+1

Nie widziałem jeszcze gotowego rozwiązania. Może gc.get_referents zamiast dir doprowadzi Cię nieco dalej. – Pankrat

+0

Zmniejsza to wartość parametru cruft (__eq__ itd.), Ale po cenie, która format zwracanych zmian zmienia się w zależności od typu obiektu, więc prawdopodobnie będzie to szybsze rozwiązanie, ale nie będzie prostsze. To też nie obsługuje rekursji. Dzięki! –

+1

Czy rozważyłeś podklasę klasy 'pickle.Pickler'? Źródło znajduje się w '.../Lib/pickle.py'. Takie postępowanie powinno pozwolić ci na ponowne użycie dużej ilości kodu i pułapkę "PickleError", aby zrobić to, co opisujesz - jak również rezygnację ze świnki z dobrze ugruntowanego protokołu piklingowania, który już posiada Python. – martineau

Odpowiedz

2

To wydaje się być początek odpowiedzi. Musiałem przenieść niektóre elementy z Python 3.2 inspect.getattr_static, aby działało, więc nie wywoływało właściwości, które tylko generowały nowe obiekty. Oto kod wymyśliłem:

#------------------------------------------------------------------------------- 
# 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 

Kod ten pozwala uruchomić coś takiego:

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()) 

a otrzymasz:

(<__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')") 

Albo uruchomić:

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

A otrzymasz:

(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]") 

Wciąż próbuję zrozumieć, dlaczego nie znaleziono pliku .abby, ale sądzę, że warto go publikować nawet w tym momencie, ponieważ jest on o wiele bardziej na dobrej drodze, niż kiedy zaczynałem.