交互式绘图无法使用来自另一个程序的管道输入进行初始化
Interactive plot fails to initialize with input piped from another program
我写了一个 Python 脚本,它应该 运行 一个循环,从标准输入读取 3d 坐标,并将最近读取的坐标显示为 [=16= 上的散点图].
当我通过提示手动输入坐标时它有效(虽然它给出了一些弃用警告):
$ python plot3d.py
.5 .5 .5
/usr/lib/pymodules/python2.7/matplotlib/backend_bases.py:2407: MatplotlibDeprecationWarning: Using default event loop until function specific to this GUI is implemented
warnings.warn(str, mplDeprecation)
当我将文件中的坐标输入程序时,它也有效:
$ head generated
0.56 0.40 0.55
0.61 0.49 0.60
0.48 0.39 0.48
0.39 0.33 0.39
0.32 0.28 0.32
0.35 0.31 0.35
0.50 0.47 0.50
0.40 0.38 0.40
0.37 0.35 0.37
0.51 0.50 0.51
$ python plot3d.py < generated
但是当我将生成脚本的输出直接通过管道传输到绘图脚本时它不起作用,并且生成脚本在每次迭代后执行 time.sleep()
:
$ python generate3d.py | python plot3d.py
工作行为是,图window被打开,它显示一个散点图显示一个点。不工作的行为是,图 window 打开,但它只显示灰色背景,没有轴或点。
这是绘图脚本的代码:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
plotted_items = None
while True:
if plotted_items is not None:
plotted_items.remove()
try:
x,y,z = map(float, raw_input().split())
except:
break
else:
plotted_items = ax.scatter([x],[y],[z])
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)
plt.pause(0.1)
生成脚本的代码如下:
from random import random
import time
last = None
while True:
if last is None:
last = [random(), random(), random()]
offset = random()
for i in range(len(last)):
last[i] += 0.25 * (offset - last[i])
print "%.2f %.2f %.2f" % tuple(last)
# the value of this sleep duration influences how long it takes to initialize
# the plot in the other script!?
time.sleep(0.5)
我观察到生成脚本中睡眠时间的影响可能与轴初始化所需的时间成正比。睡眠时间接近于零,几乎不需要初始化时间。睡眠时间为 0.1 时,图表显示已经需要很长时间。
我还没有探索显示数据最终出现时的延迟。
任何人都可以重现此行为吗?谁能帮我理解和解决这个问题?我做错了什么吗?
可能有用的信息:
$ lsb_release -d
Description: Ubuntu 14.04.5 LTS
$ python --version
Python 2.7.6
>>> matplotlib.__version__
'1.3.1'
我可以使用 matplotlib 版本 1.4.3 和 python 2.7 进行重现。原因似乎是由于 python stdin
读取函数阻塞直到管道关闭(描述为 here),
The origin of this problem is in the way these reading mechanisms are implemented in Python (See the discussion on this issue from Python's issue tracker). In Python 2.7.6, the implementation relies on C's stdio library.
他们在link中使用的解决方案是运行 geneate3d.py
作为子进程并设置非阻塞标志,
from subprocess import Popen, PIPE
import time
from fcntl import fcntl, F_GETFL, F_SETFL
from os import O_NONBLOCK, read
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import sys
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# run the shell as a subprocess:
p = Popen(['python', 'generate3d.py'],
stdin = PIPE, stdout = PIPE, stderr = PIPE, shell = False)
# set the O_NONBLOCK flag of p.stdout file descriptor:
flags = fcntl(p.stdout, F_GETFL) # get current p.stdout flags
fcntl(p.stdout, F_SETFL, flags | O_NONBLOCK)
# issue command:
p.stdin.write('command\n')
# let the shell output the result:
time.sleep(0.1)
plotted_items = None
while True:
if plotted_items is not None:
try:
plotted_items.remove()
except ValueError:
print(plotted_items)
pass
try:
x,y,z = map(float, read(p.stdout.fileno(), 1024).split(' '))
except OSError:
time.sleep(0.5)
except:
raise
else:
plotted_items = ax.scatter([x],[y],[z])
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)
plt.pause(0.1)
plt.draw()
您需要在generate3d.py
中打印后添加sys.stdout.flush()
。
似乎应该可以用 flags = fcntl(sys.stdin, F_GETFL)
和 fcntl(sys.stdin, F_SETFL, flags | O_NONBLOCK)
之类的东西在 sys.stdin
中设置 O_NONBLOCK
标志。但是,这对我不起作用。根据 bug tracker.
,我认为这在 python 3.0 中不再是问题
显然,标准输出上的写入进程缓冲区在达到一定大小之前不会被刷新。添加显式刷新解决了问题:
from random import random
import time
import sys
last = None
while True:
if last is None:
last = [random(), random(), random()]
offset = random()
for i in range(len(last)):
last[i] += 0.25 * (offset - last[i])
print "%.2f %.2f %.2f" % tuple(last)
# flush before sleep to make sure the data is actually written on time
sys.stdout.flush()
time.sleep(1.0)
或者,原始脚本可以是 运行 Python 的无缓冲模式:
$ python -u generate3d.py | python plot3d.py
我写了一个 Python 脚本,它应该 运行 一个循环,从标准输入读取 3d 坐标,并将最近读取的坐标显示为 [=16= 上的散点图].
当我通过提示手动输入坐标时它有效(虽然它给出了一些弃用警告):
$ python plot3d.py
.5 .5 .5
/usr/lib/pymodules/python2.7/matplotlib/backend_bases.py:2407: MatplotlibDeprecationWarning: Using default event loop until function specific to this GUI is implemented
warnings.warn(str, mplDeprecation)
当我将文件中的坐标输入程序时,它也有效:
$ head generated
0.56 0.40 0.55
0.61 0.49 0.60
0.48 0.39 0.48
0.39 0.33 0.39
0.32 0.28 0.32
0.35 0.31 0.35
0.50 0.47 0.50
0.40 0.38 0.40
0.37 0.35 0.37
0.51 0.50 0.51
$ python plot3d.py < generated
但是当我将生成脚本的输出直接通过管道传输到绘图脚本时它不起作用,并且生成脚本在每次迭代后执行 time.sleep()
:
$ python generate3d.py | python plot3d.py
工作行为是,图window被打开,它显示一个散点图显示一个点。不工作的行为是,图 window 打开,但它只显示灰色背景,没有轴或点。
这是绘图脚本的代码:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
plotted_items = None
while True:
if plotted_items is not None:
plotted_items.remove()
try:
x,y,z = map(float, raw_input().split())
except:
break
else:
plotted_items = ax.scatter([x],[y],[z])
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)
plt.pause(0.1)
生成脚本的代码如下:
from random import random
import time
last = None
while True:
if last is None:
last = [random(), random(), random()]
offset = random()
for i in range(len(last)):
last[i] += 0.25 * (offset - last[i])
print "%.2f %.2f %.2f" % tuple(last)
# the value of this sleep duration influences how long it takes to initialize
# the plot in the other script!?
time.sleep(0.5)
我观察到生成脚本中睡眠时间的影响可能与轴初始化所需的时间成正比。睡眠时间接近于零,几乎不需要初始化时间。睡眠时间为 0.1 时,图表显示已经需要很长时间。 我还没有探索显示数据最终出现时的延迟。
任何人都可以重现此行为吗?谁能帮我理解和解决这个问题?我做错了什么吗?
可能有用的信息:
$ lsb_release -d
Description: Ubuntu 14.04.5 LTS
$ python --version
Python 2.7.6
>>> matplotlib.__version__
'1.3.1'
我可以使用 matplotlib 版本 1.4.3 和 python 2.7 进行重现。原因似乎是由于 python stdin
读取函数阻塞直到管道关闭(描述为 here),
The origin of this problem is in the way these reading mechanisms are implemented in Python (See the discussion on this issue from Python's issue tracker). In Python 2.7.6, the implementation relies on C's stdio library.
他们在link中使用的解决方案是运行 geneate3d.py
作为子进程并设置非阻塞标志,
from subprocess import Popen, PIPE
import time
from fcntl import fcntl, F_GETFL, F_SETFL
from os import O_NONBLOCK, read
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import sys
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# run the shell as a subprocess:
p = Popen(['python', 'generate3d.py'],
stdin = PIPE, stdout = PIPE, stderr = PIPE, shell = False)
# set the O_NONBLOCK flag of p.stdout file descriptor:
flags = fcntl(p.stdout, F_GETFL) # get current p.stdout flags
fcntl(p.stdout, F_SETFL, flags | O_NONBLOCK)
# issue command:
p.stdin.write('command\n')
# let the shell output the result:
time.sleep(0.1)
plotted_items = None
while True:
if plotted_items is not None:
try:
plotted_items.remove()
except ValueError:
print(plotted_items)
pass
try:
x,y,z = map(float, read(p.stdout.fileno(), 1024).split(' '))
except OSError:
time.sleep(0.5)
except:
raise
else:
plotted_items = ax.scatter([x],[y],[z])
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)
plt.pause(0.1)
plt.draw()
您需要在generate3d.py
中打印后添加sys.stdout.flush()
。
似乎应该可以用 flags = fcntl(sys.stdin, F_GETFL)
和 fcntl(sys.stdin, F_SETFL, flags | O_NONBLOCK)
之类的东西在 sys.stdin
中设置 O_NONBLOCK
标志。但是,这对我不起作用。根据 bug tracker.
显然,标准输出上的写入进程缓冲区在达到一定大小之前不会被刷新。添加显式刷新解决了问题:
from random import random
import time
import sys
last = None
while True:
if last is None:
last = [random(), random(), random()]
offset = random()
for i in range(len(last)):
last[i] += 0.25 * (offset - last[i])
print "%.2f %.2f %.2f" % tuple(last)
# flush before sleep to make sure the data is actually written on time
sys.stdout.flush()
time.sleep(1.0)
或者,原始脚本可以是 运行 Python 的无缓冲模式:
$ python -u generate3d.py | python plot3d.py