Python closure, local variable scope error

北战南征 提交于 2019-12-17 22:03:14

问题


My function throw me with the local variable 'pt' referenced before assignment error:

Traceback (most recent call last):
  File "/home/solesschong/Workspace/PenPal/python/main.py", line 126, in callback
    ind = (i+pt) % n
UnboundLocalError: local variable 'pt' referenced before assignment

the code is as follows

def get_audio_callback(pt):

    def callback(in_data, frame_count, time_info, status):

        for i in range(frame_count):
            ind = (i+pt) % n

        return (a, b)

    return callback

and in global scope,

pt = 0
stream = p.open(stream_callback=get_audio_callback(pt))

I cannot figure out why the error occurs, since I've checked with some examples on closure and find no difference.

Edit

The reason why you cannot reproduce the error might because of the over-simplify, as mentioned by @Martijn Pieters. Hence the original code.

Further I've solved this problem by passing by reference, plz see my own answer.

"""
Sound API
"""
def get_audio_callback(pt):

    def callback(in_data, frame_count, time_info, status):
        """
        This is the callback function for sound API
        In each call, synthesized data is dumpped into the sound buffer
        """        

        wave = np.ndarray((frame_count, 2))
        for i in range(frame_count):
            ind = (i+pt) % n
            wave[i,0] = float(x[ind]) * 2
            wave[i,1] = float(y[ind]) * 2
        pt = pt + frame_count

        return (encode(wave), pyaudio.paContinue)

    return callback


p = pyaudio.PyAudio()
pt = 0

stream = p.open(format=pyaudio.paFloat32,
                channels=2,
                rate=RATE,
                output=True,
                stream_callback=get_audio_callback(pt))

回答1:


Your code assigns to pt in callback; Python determines the scope of a name at compile time and assignment makes this a local name.

pt = pt + frame_count

Unless you tell Python otherwise, that is. In Python 2, you can only mark a name explicitly as a global instead, you need Python 3 to be able to use the nonlocal keyword:

def callback(in_data, frame_count, time_info, status):
    """
    This is the callback function for sound API
    In each call, synthesized data is dumpped into the sound buffer
    """        

    nonlocal pt

    wave = np.ndarray((frame_count, 2))
    for i in range(frame_count):
        ind = (i+pt) % n
        wave[i,0] = float(x[ind]) * 2
        wave[i,1] = float(y[ind]) * 2
    pt = pt + frame_count

    return (encode(wave), pyaudio.paContinue)

With the nonlocal pt line Python is explicitly told not to treat pt as a local name but to take it from the enclosing scope of get_audio_callback instead.

In Python 2, you can just create a local that takes its value from the closure:

def callback(in_data, frame_count, time_info, status):
    """
    This is the callback function for sound API
    In each call, synthesized data is dumpped into the sound buffer
    """        

    pt_local = pt

    wave = np.ndarray((frame_count, 2))
    for i in range(frame_count):
        ind = (i+pt_local) % n
        wave[i,0] = float(x[ind]) * 2
        wave[i,1] = float(y[ind]) * 2
    pt_local = pt_local + frame_count

    return (encode(wave), pyaudio.paContinue)

because your enclosing get_audio_callback scope doesn't appear to use pt anyway and won't need access to the updated pt_local value.

If you do need pt to update at the get_audio_callback scope (if, say, callback is called multiple times and you need pt to be updated from call to call), you need to avoid using pt as a local inside the callback function altogether.

One effective work-around for that is to wrap the value in a mutable object or assign it as a mutable attribute somewhere that both the enclosing scope and the local scope can access it without it ever being seen as a local assignment. Setting an attribute on the callback function is a good way to do that:

def get_audio_callback(pt):

    def callback(in_data, frame_count, time_info, status):
        """
        This is the callback function for sound API
        In each call, synthesized data is dumpped into the sound buffer
        """        

        wave = np.ndarray((frame_count, 2))
        for i in range(frame_count):
            ind = (i+callback.pt) % n
            wave[i,0] = float(x[ind]) * 2
            wave[i,1] = float(y[ind]) * 2
        callback.pt = callback.pt + frame_count

        return (encode(wave), pyaudio.paContinue)

    callback.pt = pt

    return callback

Here callback.pt is no longer a local name; it is an attribute on the callback function object instead.




回答2:


It turned out to be a problem about 'reference'

I changed my code into passing pt by variable, and it worked out fine.

pt = [0]

def get_audio_callback(pt_ref):

    def callback(in_data, frame_count, time_info, status):

        pt = pt_ref[0]

        for i in range(frame_count):
            ind = (i+pt) % n

        return (a, b)

    return callback


来源:https://stackoverflow.com/questions/23170754/python-closure-local-variable-scope-error

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!