有没有办法声明一个函数应该使用调用者的作用域? [英] Is there a way to declare that a function should use the scope of the caller?

查看:84
本文介绍了有没有办法声明一个函数应该使用调用者的作用域?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有与C宏类似的功能,可让您以内联方式重用代码,而无需为该代码段创建单独的作用域?

is there a feautre similar to C macros which lets you reuse code in an inline manner, without creating a seperate scope for that piece of code?

例如:

a=3
def foo():
    a=4
foo()
print a

将打印3,但是我希望它打印4.

will print 3, however i want it to print 4.

我知道涉及类或全局dict之类的对象的解决方案,但是我正在寻找更原始的解决方案(例如,函数装饰器),该解决方案只允许我在调用者的范围内进行更改.

i am aware of solutions involving objects like classes or a global dict, however i'm looking for a more primitive solution (like a function decorator for example) that would simply let me make changes inside the scope of the caller instead.

非常感谢

任何需要声明要使用哪些变量或事先声明名称空间"(如mutabale对象)的解决方案都不是我想要的解决方案.

我自己尝试过:

def pgame():
a=3
c=5
print locals()
game(a)
print locals()


class inline_func(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):
        return self.f(*args, **kwargs)


#to be @inline_func
def game(b, a=4):
    exec("inspect.stack()[3][0].f_locals.update(inspect.stack()[1] [0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))\ninspect.stack()[1][0].f_locals.update(inspect.stack()[3][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[1][0]),ctypes.c_int(0))")
try:
    print "your code here"
finally:
    exec("inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))")

@inline_func
def strip_game(b, a=4):
    print "your code here"

但是我遇到了一个严重的问题,即如何在不破坏程序的可调试性的情况下将代码注入到strip_game中,因为我只是想创建一个新的代码对象或使用exec,而这两个问题都存在一些严重的问题.

but i have ran into a serious problem with how to inject code into strip_game without ruining the debugability of the program, because i had only thought of creating a new code object or using exec, both suffering from some severe problems.

好的,所以我有一些接近可行的解决方案,但是我遇到了一个非常棘手的问题:

ok, so i have something close to a working solution, however i encounter a very wierd problem:

import inspect
import ctypes
import struct
import dis
import types



def cgame():
    a=3
    c=5
    print locals()
    strip_game(a)
    print locals()


def pgame():
    a=3
    c=5
    print locals()
    game(a)
    print locals()


class empty_deco(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):
        return self.f(*args, **kwargs)

debug_func = None
class inline_func(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):

        init_exec_string = "inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\n" + \
                           "ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))\n" + \
                           "inspect.stack()[1][0].f_locals.update(inspect.stack()[3][0].f_locals)\n" + \
                           "ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[1][0]),ctypes.c_int(0))"
        fini_exec_string = "inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\n" + \
                           "ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))" 

        co_stacksize = max(6, self.f.func_code.co_stacksize) # make sure we have enough space on the stack for everything
        co_consts = self.f.func_code.co_consts +(init_exec_string, fini_exec_string)
        init = "d"  + struct.pack("H", len(strip_game.f.func_code.co_consts)) #LOAD_CONST init_exec_string
        init += "d\x00\x00\x04U" # LOAD_CONST None, DUP_TOP, EXEC_STMT
        init += "z" + struct.pack("H", len(self.f.func_code.co_code) + 4) #SETUP_FINALLY

        fini = "Wd\x00\x00" # POP_BLOCK, LOAD_CONST None
        fini += "d" + struct.pack("H", len(strip_game.f.func_code.co_consts) + 1) #LOAD_CONST fini_exec_string
        fini += "d\x00\x00\x04UXd\x00\x00S" # LOAD_CONST None, DUP_TOP, EXEC_STMT, END_FINALLY, LOAD_CONST None, RETURN
        co_code = init + self.f.func_code.co_code + fini
        co_lnotab = "\x00\x00\x0b" + self.f.func_code.co_lnotab[1:] # every error in init will be attributed to @inline_func, errors in the function will be treated as expected, errors in fini will be attributed to the last line probably.
        new_code = types.CodeType(
        self.f.func_code.co_argcount,
        self.f.func_code.co_nlocals,
        co_stacksize,
        self.f.func_code.co_flags & ~(1), # optimized functions are problematic for us
        co_code,
        co_consts,
        self.f.func_code.co_names,
        self.f.func_code.co_varnames,
        self.f.func_code.co_filename,
        self.f.func_code.co_name,
        self.f.func_code.co_firstlineno,
        co_lnotab,
        self.f.func_code.co_freevars,
        self.f.func_code.co_cellvars,)

        self.inline_f =  types.FunctionType(new_code, self.f.func_globals, self.f.func_name, self.f.func_defaults, self.f.func_closure)
        #dis.dis(self.inline_f)
        global debug_func
        debug_func = self.inline_f
        return self.inline_f(*args, **kwargs)


@empty_deco
def game(b, a=4):
    exec("inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))\ninspect.stack()[1][0].f_locals.update(inspect.stack()[3][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[1][0]),ctypes.c_int(0))")
    try:
        print "inner locals:"
        print locals()
        print c
        return None
    finally:
        exec("inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))")

@inline_func
def strip_game(b, a=4):
    print "inner locals:"
    print locals()
    print c
    return None



def stupid():
    exec("print 'hello'")
    try:
        a=1
        b=2
        c=3
        d=4
    finally:
        exec("print 'goodbye'")

现在这个似乎可以工作了,但我得到了以下信息:

now this seems to work however, i get the following:

>>>cgame()
{'a': 3, 'c': 5}
{'a': 4, 'c': 5, 'b': 3}
your code here

Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    cgame()
  File "C:\Python27\somefile.py", line 14, in cgame
    strip_game(a)
  File "C:\Python27\somefile.py", line 78, in __call__
    return self.inline_f(*args, **kwargs)
  File "C:\Python27\somefile.py", line 94, in strip_game
    z = c
NameError: global name 'c' is not defined

现在,当我反汇编函数时,我在gamestrip_game之间得到以下非常奇怪的编译区别:

now when i disassemble the functions, i get the following very wierd compilation difference between game and strip_game:

在游戏中:

86          16 LOAD_NAME                0 (locals)
             19 CALL_FUNCTION            0
             22 PRINT_ITEM          
             23 PRINT_NEWLINE       

 87          24 **LOAD_NAME**                1 (c)
             27 PRINT_ITEM          
             28 PRINT_NEWLINE       

在脱衣舞游戏中:

95          16 LOAD_GLOBAL              0 (locals)
             19 CALL_FUNCTION            0
             22 PRINT_ITEM          
             23 PRINT_NEWLINE       

 96          24 LOAD_GLOBAL              1 (c)
             27 PRINT_ITEM          
             28 PRINT_NEWLINE       

为什么会出现这种差异?

why is does this difference occur?

推荐答案

好的,所以在坐了几个小时之后,我设法写了一个解决方案,在解决这个问题时有一些重大陷阱,我会指出他们在下面

ok, so after several hours of sitting on this thing i've managed to write a solution, there are some major pitfalls when approaching this and i'll note them below

import inspect
import ctypes
import struct
import dis
import types

def dump(obj):
  for attr in dir(obj):
    print("obj.%s = %r" % (attr, getattr(obj, attr)))

def cgame():
    a=3
    c=5
    print locals()
    strip_game(a)
    print locals()


def pgame():
    a=3
    c=5
    print locals()
    game(a)
    print locals()


class empty_deco(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):
        return self.f(*args, **kwargs)



debug_func = None
class inline_func(object):
    def __init__(self, f):
        self.f = f
    # this is the price we pay for using 2.7
    # also, there is a huge glraing issue here, which is what happens if the user TRIES to access a global variable?
    @staticmethod
    def replace_globals_with_name_lookups(co):
        res = ""
        code = list(co)
        n = len(code)
        i = 0
        while i < n:
            c = code[i]
            op = ord(c)
            if dis.opname[op] == "STORE_GLOBAL":
                code[i] = chr(dis.opmap['STORE_NAME'])
            elif dis.opname[op] == "DELETE_GLOBAL":
                code[i] = chr(dis.opmap['DELETE_NAME'])
            elif dis.opname[op] == "LOAD_GLOBAL":
                code[i] = chr(dis.opmap['LOAD_NAME'])
            i = i+1
            if op >= dis.HAVE_ARGUMENT:
                i = i+2
        return "".join(code)

    def __call__(self, *args, **kwargs):

        init_exec_string = "inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\n" + \
                           "ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))\n" + \
                           "inspect.stack()[1][0].f_locals.update(inspect.stack()[3][0].f_locals)\n" + \
                           "ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[1][0]),ctypes.c_int(0))"
        fini_exec_string = "inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\n" + \
                           "ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))" 

        co_stacksize = max(6, self.f.func_code.co_stacksize) # make sure we have enough space on the stack for everything
        co_consts = self.f.func_code.co_consts +(init_exec_string, fini_exec_string)
        init = "d"  + struct.pack("H", len(strip_game.f.func_code.co_consts)) #LOAD_CONST init_exec_string
        init += "d\x00\x00\x04U" # LOAD_CONST None, DUP_TOP, EXEC_STMT
        init += "z" + struct.pack("H", len(self.f.func_code.co_code) + 4) #SETUP_FINALLY

        fini = "Wd\x00\x00" # POP_BLOCK, LOAD_CONST None
        fini += "d" + struct.pack("H", len(strip_game.f.func_code.co_consts) + 1) #LOAD_CONST fini_exec_string
        fini += "d\x00\x00\x04UXd\x00\x00S" # LOAD_CONST None, DUP_TOP, EXEC_STMT, END_FINALLY, LOAD_CONST None, RETURN
        co_code = init + self.replace_globals_with_name_lookups(self.f.func_code.co_code) + fini
        co_lnotab = "\x00\x00\x0b" + self.f.func_code.co_lnotab[1:] # every error in init will be attributed to @inline_func, errors in the function will be treated as expected, errors in fini will be attributed to the last line probably.
        new_code = types.CodeType(
        self.f.func_code.co_argcount,
        self.f.func_code.co_nlocals,
        co_stacksize,
        self.f.func_code.co_flags & ~(1), # optimized functions are problematic for us
        co_code,
        co_consts,
        self.f.func_code.co_names,
        self.f.func_code.co_varnames,
        self.f.func_code.co_filename,
        self.f.func_code.co_name,
        self.f.func_code.co_firstlineno,
        co_lnotab,
        self.f.func_code.co_freevars,
        self.f.func_code.co_cellvars,)

        self.inline_f =  types.FunctionType(new_code, self.f.func_globals, self.f.func_name, self.f.func_defaults, self.f.func_closure)
        #dis.dis(self.inline_f)
        global debug_func
        debug_func = self.inline_f
        return self.inline_f(*args, **kwargs)


@empty_deco
def game(b, a=4):
    exec("inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))\ninspect.stack()[1][0].f_locals.update(inspect.stack()[3][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[1][0]),ctypes.c_int(0))")
    try:
        print "inner locals:"
        print locals()
        print c
        return None
    finally:
        exec("inspect.stack()[3][0].f_locals.update(inspect.stack()[1][0].f_locals)\nctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(inspect.stack()[3][0]),ctypes.c_int(0))")

@inline_func
def strip_game(b, a=4):
    print "inner locals:"
    print locals()
    print c
    return None

所需的手动代码位于class inline_func和某些导入中(也许您可以将它们设置为类的内部?我真的不确定)

where the acutal code needed lies in the class inline_func and some of the imports (maybe you can make them internal to the class? i'm really not sure)

那整个事情是做什么的?好吧,它使得strip_gamegame的代码(几乎)相同,即:

so what does this whole thing do? well, it makes it so the code for strip_game and game are (nearly) identical, namely:

  1. 它会插入一个函数序言,该函数序言会更新调用方的本地语言,然后将调用方的本地语言添加到被调用方.
  2. 在功能周围插入try try块
  3. 在某些思考之后,我意识到每一个符号查找都从全局查找更改为普通(名称)查找
  4. 进入finally块后,将更新呼叫者的本地电话.

在做类似这样的事情时有一些重大陷阱,我将列出一些我遇到的问题:

there are some major pitfalls making things like these, i'll list a few problems i've encountered:

  1. cpython compiler_nameop函数基于给定函数的简单性优化名称空间查找,这意味着它将在可能的情况下将名称查找优化为全局查找
  2. 更改字节码意味着影响程序的调试能力,我已经在co_lnotab变量中解决了此问题
  3. 对于大型函数,此解决方案将无法使用,因为某些操作码将不得不使用extended_args:即,变量的负载和try-finally块(无论如何,都可以通过使用extended_args来解决这一点...)
  1. cpython compiler_nameop function optimizes namespace lookup based on the simplicity of the given function, that means that it will optimize name lookups to global lookups if it can
  2. changing the bytecode means affecting the debug-ability of the program, i had addressed this in the co_lnotab variable
  3. for large functions this solution won't work as some of the opcodes would have to use extended_args: namely, the loads of the variables and the try-finally block (this point is solvable by using extended_args anyways...)

感谢@jsbueno抽出时间并把我指向PyFrame_LocalsToFast.

thank @jsbueno for putting in the time and pointing me to PyFrame_LocalsToFast.

P.S.此解决方案适用于python 2.7.6,python在API稳定性方面存在一些问题,因此对于较新的版本,可能需要修复.

P.S. this solution works for python 2.7.6, python has some issues when it comes to stability of the API, so for newer versions this might need to be fixed.

这篇关于有没有办法声明一个函数应该使用调用者的作用域?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆