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))
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.
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