简体   繁体   中英

Python introspection

Is it possible to apply code from imported module to module which import it? For example, I have module Debug where defined some decorator for debugging, for example:

def debug_func(f):
    def wrapper(*func_args, **func_kwargs):
        print(f(*func_args, **func_kwargs))
    return wrapper

What's the idea: It's would be useful if I can just

import Debug 

and all functions from current module will wrapped with decorator. Is it possible?

Within Debug.py:

import functools
from types import FunctionType

def wrap_functions(module_dict):
    for k, f in module_dict.items():
        if not isinstance(f, FunctionType):
            continue
        def get_wrapper(f):
            def wrapper(*func_args, **func_kwargs):
                print(f(*func_args, **func_kwargs))
            return functools.wraps(f)(wrapper)
        module_dict[k] = get_wrapper(f)

At the bottom of the module you are wishing to debug:

import Debug
Debug.wrap_functions(globals())

Thanks (+1) to all the commenters who provided suggestions.

How would Debug know what module it's being imported from? Answer: It can't. How would Debug know to run more than once if it's imported into more than one module? Answer: It wouldn't; modules are only run once and then cached. So what you want to do can't be done quite as simply as you want to.

You could, however, do it by calling a function in your debug module after importing. You can pass __name__ from the calling module to provide its name, after which it's possible to get a reference to the module itself, and then the top-level variables defined in it, some of which may be functions. These may then be decorated.

# debug.py
import types, sys, functools

# decorator to be applied to all top-level functions in a module
def debug(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print "calling", fn.__name__, "with args", *args, **kwargs
        result = fn(*args, **kwargs)
        print "returning from", fn.__name__, "with return value", result
        return result

# decorate all top-level functions in named module with a given decorator
# (by default it is the above decorator but it could be a different one)
# This makes these functions behave as though they had been written with
# @debug above them.
def set_debug(modname, debug=debug):
    module = sys.modules[modname]
    for name in dir(module):
        if not name.startswith("_"):
            thing = getattr(module, name)
            if isinstance(thing, types.FunctionType):
                setattr(module, name, debug(thing))

Now in the calling module:

# main.py
import debug

def main():
    print "in main module"

debug.set_debug(__name__)   # install debugging decorator

main()

Jim Garrison's approach of passing the namespace explicitly (rather than the module name) is also good and actually simplifies things a bit; you could use it to decorate things other than modules. I've broken mine up so you can pass in a different decorator if you want.

I think the pythonic way of doing it would be to have your Debug call the debugged module, as the standard tools ( pdb , unittest , ...) with a call like:

$ python -m Debug mymodule.py

With your Debug importing/executing the module with any decorators you like. Doing it the other way (importing Debug ) messes with the order in which modules are evaluated, as you are trying to have your Debug module depend on the module importing it, which is tricky at best.

Apart from passing your functions explicitly (via globals() , a list of functions...) to some Debug.mark_as_debug as Jim Garrison suggested, I think it would make more sense to organise your code such that you can have your Debug call your modules instead of the other way around.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM