mathew gunther
mathew gunther

Reputation: 180

searching python object hierarchy

I'm using an unstable python library that's undergoing changes to the API in its various git submodules. Every few weeks, some member or method's location will get changed or renamed. For example, the expression

vk.rinkront[0].asyncamera.printout()

that worked a few weeks back, now has the asyncamera member located in

vk.toplink.enabledata.tx[0].asyncamera[0].print()

I manage to figure out the method's new location by greping, git diffing, ipython autocomplete, and generally bumbling my way around. This process is painful, because the repository has been quite abstracted and the member names observable from the python shell do not necessarily appear in the git submodule code.

Is there a python routine that performs a graph traversal of the object hierarchy while checking for keywords (e.g. print ) ? (if no, I'll hack some bfs/dfs that checks the child node type before pushing/appending the __dict__, array, dir(). etc... contents onto a queue/stack. But some one must have come across this problem before and come up with a more elegant solution.)

-------EDITS----------

to sum up, is there an existing library such that the following code would work?

import unstable_library
import object_inspecter.search

vk = unstable_library.create()  #initializing vk

object_inspecter.search(vk,"print") #searching for lost method

Upvotes: 1

Views: 279

Answers (2)

Joran Beasley
Joran Beasley

Reputation: 114038

you can use fairly simple graph traversal to do this

def find_thing(ob,pattern,seen=set(),name=None,curDepth=0,maxDepth=99,isDict=True):
  if(ob is None):
    return []
  if id(ob) in seen:
    return []
  seen.add(id(ob))
  name = str(name or getattr(ob,"__name__",str(ob)))  
  base_case = check_base_cases(ob,name,pattern)
  if base_case is not None:
     return base_case
  if(curDepth>=maxDepth):
     return []
  return recursive_step(ob,pattern,name=name,curDepth=curDepth,isDict=isDict)  

now just define your two steps (base_case and recursive_step)... something like

def check_base_cases(ob,name,pattern):
  if isinstance(ob,str):
    if re.match(pattern,ob):
      return [ob]
    else:
      return []
  if isinstance(ob,(int,float,long)):
    if ob == pattern or str(ob) == pattern:
      return [ob]
    else:
      return []
  if isinstance(ob,types.FunctionType):
    if re.match(pattern,name):
      return [name]
    else:
      return []

def recursive_step(ob,pattern,name,curDepth,isDict=True):
  matches = []
  if isinstance(ob,(list,tuple)):         
      for i,sub_ob in enumerate(ob):
            matches.extend(find_thing(sub_ob,pattern,name='%s[%s]'%(name,i),curDepth=curDepth+1))
      return matches
  if isinstance(ob,dict):
    for key,item in ob.items():
       if re.match(pattern,str(key)):
          matches.append('%s.%s'%(name,key) if not isDict else '%s["%s"]'%(name,key))
       else:
          matches.extend(find_thing(item,pattern,
                                    name='%s["%s"]'%(name,key) if isDict else '%s.%s'%(name,key),
                                    curDepth=curDepth+1))
    return matches 
  else:
    data = dict([(x,getattr(ob,x)) for x in dir(ob) if not x.startswith("_")])
    return find_thing(data,pattern,name=name,curDepth=curDepth+1,isDict=False)

finally you can test it like so

print(find_thing(vk,".*print.*"))

I used the following Example

class vk(object):
  class enabledata:
    class camera:
      class object2:
        def printX(*args):
          pass
      asynccamera = [object2]
    tx = [camera]
  toplink = {'enabledata':enabledata}

running the following

print( find_thing(vk,'.*print.*') )
#['vk.toplink["enabledata"].camera.asynccamera[0].printX']

Upvotes: 1

jhill515
jhill515

Reputation: 943

Oy, I feel for you... You probably should just lock down on a version, use it until the API becomes stable, and then upgrade later. That's why complex projects focus on dependency version control.

There is a way, but it's not really standard. The dir() built-in function returns a list of string names of all attributes and methods of a class. With a little bit a wrangling, you could write a script that recursively digs down.

The ultimate issue, though, is that you're going to run into infinite recursive loops when you try to investigate class members. You'll need to add in smarts to either recognize the pattern or limit the depth of your searching.

Upvotes: 1

Related Questions