2011-03-25 11 views
9

Estoy intentando recopilar información sobre bloqueos y tengo problemas para averiguar cómo obtener los elementos globales que se están utilizando en la función bloqueada.En Python, ¿cómo puedo obtener las variables globales que se utilizan en una función?

import inspect 

fun = 222 
other = "junk" 

def test(): 
    global fun 
    harold = 888 + fun 
    try: 
     harold/0 
    except: 
     frames = inspect.trace() 
     print "Local variables:" 
     print frames[0][0].f_locals 

     print "All global variables, not what I want!" 
     print frames[0][0].f_globals 

test() 

test() sólo utiliza la "diversión", pero f_globals da todas las variables globales disponibles. ¿Hay alguna forma de obtener solo los globales que están siendo utilizados por esta función?

+0

También utiliza 'inspeccionar', para la mayoría de las definiciones de" uso ". Si te refieres a algo más por "uso", sé específico. – delnan

+0

El propósito es eliminar todas las cosas extra en globales. Hay una biblioteca principal que se importa a través de *, por lo que hay demasiadas variables globales para informar de todas ellas. Pregunta actualizada para realmente usar 'diversión'. –

Respuesta

1

Una manera sucia sería usar inspect.getsourcelines() y buscar las líneas que contienen global <varname>. No hay buenos métodos para esto, al menos no en el módulo inspect.

0

Como ya descubrió, la propiedad f_globals le proporciona el espacio de nombres global en el que se definió la función.

Según lo que puedo ver, la única manera de descubrir qué variables globales se utilizan en realidad es desensamblar el código de bytes de la función con dis; buscar los códigos de bytes STORE_NAME, STORE_GLOBAL, DELETE_GLOBAL, etc.

3

mira esto

a = 10 

def test(): 
    global a 
    a = 12 
    b = 12 

print "co_argcount = ",test.__code__.co_argcount 
print "co_cellvars = ",test.__code__.co_cellvars 
print "co_code = ",test.__code__.co_code 
print "co_consts = ",test.__code__.co_consts 
print "co_filename = ",test.__code__.co_filename 
print "co_firstlineno = ",test.__code__.co_firstlineno 
print "co_flags = ",test.__code__.co_flags 
print "co_freevars = ",test.__code__.co_freevars 
print "co_lnotab = ",test.__code__.co_lnotab 
print "co_name = ",test.__code__.co_name 
print "co_names = ",test.__code__.co_names 
print "co_nlocals = ",test.__code__.co_nlocals 
print "co_stacksize = ",test.__code__.co_stacksize 
print "co_varnames = ",test.__code__.co_varnames 
+0

es posible que desee mencionar esto requiere Python 2.6 o superior. –

+1

¡Gracias! Afortunadamente estoy usando 2.6 así 'test .__ code __. Co_names' era exactamente lo que estaba buscando. En este caso específico, estoy decorando funciones para manejarlas si fallan, así que tengo la función a mano. –

1

lo necesitaba también a mí mismo. Esta es mi solución. El camino rápido no cubre la mayoría de los casos es probable que esté interesado.

def iterGlobalsUsedInFunc(f, fast=False, loadsOnly=True): 
    if hasattr(f, "func_code"): code = f.func_code 
    else: code = f 
    if fast: 
     # co_names is the list of all names which are used. 
     # These are mostly the globals. These are also attrib names, so these are more... 
     for name in code.co_names: 
      yield name 
    else: 
     # Use the disassembly. Note that this will still not 
     # find dynamic lookups to `globals()` 
     # (which is anyway not possible to detect always). 
     import dis 
     ops = ["LOAD_GLOBAL"] 
     if not loadsOnly: 
      ops += ["STORE_GLOBAL", "DELETE_GLOBAL"] 
     ops = map(dis.opmap.__getitem__, ops) 
     i = 0 
     while i < len(code.co_code): 
      op = ord(code.co_code[i]) 
      i += 1 
      if op >= dis.HAVE_ARGUMENT: 
       oparg = ord(code.co_code[i]) + ord(code.co_code[i+1])*256 
       i += 2 
      else: 
       oparg = None 
      if op in ops: 
       name = code.co_names[oparg] 
       yield name 

    # iterate through sub code objects 
    import types 
    for subcode in code.co_consts: 
     if isinstance(subcode, types.CodeType): 
      for g in iterGlobalsUsedInFunc(subcode, fast=fast, loadsOnly=loadsOnly): 
       yield g 

Una versión actualizada podría ser here.


Mi caso de uso:

tengo algún módulo (songdb) que tiene algunos objetos de bases de datos globales y quería cargar con pereza una vez llamé a una función que utiliza la variable de base de datos global. Podría haber decorado manualmente tales funciones con un cargador lento o podría detectar automáticamente qué funciones lo necesitan con mi función iterGlobalsUsedInFunc.

Esto es básicamente el código (full code; fue realmente extendieron para las clases de ahora), donde init decora automáticamente tales funciones:

DBs = { 
    "songDb": "songs.db", 
    "songHashDb": "songHashs.db", 
    "songSearchIndexDb": "songSearchIndex.db", 
    } 
for db in DBs.keys(): globals()[db] = None 

def usedDbsInFunc(f): 
    dbs = [] 
    for name in utils.iterGlobalsUsedInFunc(f, loadsOnly=True): 
     if name in DBs: 
      dbs += [name] 
    return dbs 

def init(): 
    import types 
    for fname in globals().keys(): 
     f = globals()[fname] 
     if not isinstance(f, types.FunctionType): continue 
     dbs = usedDbsInFunc(f) 
     if not dbs: continue 
     globals()[fname] = lazyInitDb(*dbs)(f) 

def initDb(db): 
    if not globals()[db]: 
     globals()[db] = DB(DBs[db]) 

def lazyInitDb(*dbs): 
    def decorator(f): 
     def decorated(*args, **kwargs): 
      for db in dbs: 
       initDb(db) 
      return f(*args, **kwargs) 
     return decorated 
    return decorator 

Otra solución habría sido utilizar un proxy de objeto que carga con pereza la base de datos. Lo he usado en otro lugar de este proyecto, así que también he implementado dicho proxy de objeto; si está interesado, consulte aquí: utils.py: ObjectProxy.

Cuestiones relacionadas