如何访问(和编辑)回调函数中的变量?

8 投票
3 回答
10937 浏览
提问于 2025-04-16 10:43

我在用Boto访问亚马逊的S3存储服务。在上传文件时,我可以设置一个回调函数。不过,问题是我无法在这个回调函数里访问到需要的变量,除非我把它们设为全局变量。这样一来,如果我把它们设为全局变量,那它们对其他的Celery任务也是全局的(直到我重启Celery),因为文件上传是通过Celery任务来执行的。

下面是一个上传JSON文件的函数,这个文件里包含了视频转换进度的信息。

def upload_json():
    global current_frame
    global path_to_progress_file
    global bucket
    json_file = Key(bucket)
    json_file.key = path_to_progress_file
    json_file.set_contents_from_string('{"progress": "%s"}' % current_frame,
    cb=json_upload_callback, num_cb=2, policy="public-read")

接下来是两个回调函数,用于上传在视频转换过程中由ffmpeg生成的帧和包含进度信息的JSON文件。

# Callback functions that are called by get_contents_to_filename.
# The first argument is representing the number of bytes that have
# been successfully transmitted from S3 and the second is representing
# the total number of bytes that need to be transmitted.
def frame_upload_callback(transmitted, to_transmit):
    if transmitted == to_transmit:
        upload_json()
def json_upload_callback(transmitted, to_transmit):
    global uploading_frame
    if transmitted == to_transmit:
        print "Frame uploading finished"
        uploading_frame = False

理论上,我可以把上传帧的变量传递给upload_json函数,但它不会传递到json_upload_callback,因为这个回调是由Boto执行的。

实际上,我可以写成这样。

In [1]: def make_function(message):
   ...:     def function():
   ...:         print message
   ...:     return function
   ...: 

In [2]: hello_function = make_function("hello")

In [3]: hello_function
Out[3]: <function function at 0x19f4c08>

In [4]: hello_function()
hello

不过,这样做只能读取这个值,而不能修改它。

def myfunc():
    stuff = 17
    def lfun(arg):
        print "got arg", arg, "and stuff is", stuff
    return lfun

my_function = myfunc()
my_function("hello")

这样是可以工作的。

def myfunc():
    stuff = 17
    def lfun(arg):
        print "got arg", arg, "and stuff is", stuff
        stuff += 1
    return lfun

my_function = myfunc()
my_function("hello")

而这样会出现一个错误:UnboundLocalError:在赋值之前引用了局部变量'stuff'。

谢谢。

3 个回答

0

做这些事情的一个简单方法是使用一个本地函数。

def myfunc():
    stuff = 17
    def lfun(arg):
        print "got arg", arg, "and stuff is", stuff
        stuff += 1
    def register_callback(lfun)

每次你调用myfunc的时候,它都会创建一个新的函数,并且这个新函数可以使用本地的“东西”的副本。

4

你可以通过 functools.partial 来创建一个部分函数。这是一种在调用函数时,提前把一些变量放进去的方法。不过,要让这个方法有效,你需要把一个可变的值,比如列表或字典,传递给函数,而不是仅仅传一个布尔值。

from functools import partial
def callback(arg1, arg2, arg3):
    arg1[:] = [False]
    print arg1, arg2, arg3

 local_var = [True]   
 partial_func = partial(callback, local_var)

 partial_func(2, 1)
 print local_var  # prints [False]
16

在Python 2.x中,闭包中的变量是只读的(这不是因为Python虚拟机的原因,而是因为语法不允许对非本地和非全局变量进行写操作)。

不过,你可以在闭包中使用可变的值,也就是说……

def myfunc():
    stuff = [17] # <<---- this is a mutable object
    def lfun(arg):
        print "got arg", arg, "and stuff[0] is", stuff[0]
        stuff[0] += 1
    return lfun

my_function = myfunc()
my_function("hello")
my_function("hello")

如果你使用的是Python 3.x,可以使用关键字nonlocal来指定在闭包中读写的变量不是本地变量,而是应该从外层作用域中获取的:

def myfunc():
    stuff = 17
    def lfun(arg):
        nonlocal stuff
        print "got arg", arg, "and stuff is", stuff
        stuff += 1
    return lfun

my_function = myfunc()
my_function("hello")
my_function("hello")

撰写回答