Injecting variables into the caller's scope?

前端 未结 3 424
故里飘歌
故里飘歌 2020-12-25 15:29

Can I define a function which, when called, inserts new locals into the caller\'s scope? I have a feeling that passing the caller\'s locals() into the function migh

相关标签:
3条回答
  • 2020-12-25 15:47

    By Python's rules, you cannot alter your caller's locals; in the current implementations, if you try (e.g. with the black magic Anurag suggests) you will not get an exception (though I'd like to add that error check to some future version), but it will be essentially inoperative if your caller is a function (not if your caller is module top-level code) -- the caller's actual local variables won't in fact be affected. This holds whether the caller's locals are explicitly passed in, or fetched through black magic: they still need to be treated as a read-only dict if your code is to have any sanity.

    Rather, you could have the caller pass in an explicit, real, normal dict (which could be initialized from locals() if you want), and all alterations your code does in that dict will still be there for the caller's use -- just not as "new barenames" in the caller's local scope of course, but the functionality is the same whether the caller needs to use x['foo'] or x.foo or (as you'd prefer) just barename foo.

    BTW, to use attribute-access syntax rather than dict indexing syntax, you can do:

    class Bunch(object): pass
    
    ...
    
    # caller code
    b = Bunch()
    thefun(b)
    print b.foo
    
    ...
    
    # called function
    def thefun(b):
      b.foo = 23
    

    This also covers, with a tiny variation, the case in which thefun wants to work with dict indexing syntax (say its body is b['foo'] = 23 instead of b.foo = 23): in that case, the caller just needs to use thefun(vars(b)) instead of the plain thefun(b), but it can keep working with the b.foo access syntax afterwards.

    0 讨论(0)
  • 2020-12-25 15:50

    Check out the inspect module, it is used by minimock to mock the caller's scope.

    This code ought to do what you want exactly:

    import inspect
    def mess_with_caller():
        stack = inspect.stack()
        try:
            locals_ = stack[1][0].f_locals
        finally:
            del stack
        locals_['my_new_function'] = lambda : 'yaaaaaay'
    
    
    mess_with_caller()
    print my_new_function()
    >>> Output: 'yaaaaaay'
    
    0 讨论(0)
  • 2020-12-25 16:05

    This is not possible without changing the API, as CPython retains a reference to the argument list for the duration of the function call.

    One way to do this is to pass an object, and clear the object's internal state when done using it.

    class Obj(object):
        def __init__(self, state):
            self.state = state
    
    def f(o):
        # Do stuff with o.state
        del o.state
    
    0 讨论(0)
提交回复
热议问题