Is it possible to modify variable in python that is in outer, but not global, scope?

前端 未结 9 1971
温柔的废话
温柔的废话 2020-11-30 02:55

Given following code:

def A() :
    b = 1

    def B() :
        # I can access \'b\' from here.
        print( b )
        # But can i modify \'b\' here? \'         


        
9条回答
  •  猫巷女王i
    2020-11-30 03:31

    The short answer that will just work automagically

    I created a python library for solving this specific problem. It is released under the unlisence so use it however you wish. You can install it with pip install seapie or check out the home page here https://github.com/hirsimaki-markus/SEAPIE

    user@pc:home$ pip install seapie

    from seapie import Seapie as seapie
    def A():
        b = 1
    
        def B():
            seapie(1, "b=2")
            print(b)
    
        B()
    A()
    

    outputs

    2
    

    the arguments have following meaning:

    • The first argument is execution scope. 0 would mean local B(), 1 means parent A() and 2 would mean grandparent aka global
    • The second argument is a string or code object you want to execute in the given scope
    • You can also call it without arguments for interactive shell inside your program

    The long answer

    This is more complicated. Seapie works by editing the frames in call stack using CPython api. CPython is the de facto standard so most people don't have to worry about it.

    The magic words you are probably most likely interesed in if you are reading this are the following:

    frame = sys._getframe(1)          # 1 stands for previous frame
    parent_locals = frame.f_locals    # true dictionary of parent locals
    parent_globals = frame.f_globals  # true dictionary of parent globals
    
    exec(codeblock, parent_globals, parent_locals)
    
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
    # the magic value 1 stands for ability to introduce new variables. 0 for update-only
    

    The latter will force updates to pass into local scope. local scopes are however optimized differently than global scope so intoducing new objects has some problems when you try to call them directly if they are not initialized in any way. I will copy few ways to circumvent these problems from the github page

    • Assingn, import and define your objects beforehand
    • Assingn placeholder to your objects beforehand
    • Reassign object to itself in main program to update symbol table: x = locals()["x"]
    • Use exec() in main program instead of directly calling to avoid optimization. Instead of calling x do: exec("x")

    If you are feeling that using exec() is not something you want to go with you can emulate the behaviour by updating the the true local dictionary (not the one returned by locals()). I will copy an example from https://faster-cpython.readthedocs.io/mutable.html

    import sys
    import ctypes
    
    def hack():
        # Get the frame object of the caller
        frame = sys._getframe(1)
        frame.f_locals['x'] = "hack!"
        # Force an update of locals array from locals dict
        ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                              ctypes.c_int(0))
    
    def func():
        x = 1
        hack()
        print(x)
    
    func()
    

    Output:

    hack!
    

提交回复
热议问题