Output 小部件¶
参考 examples/Output Widget.ipynb
import ipywidgets as widgets
Output
小部件可以捕获和显示 stdout、stderr 和 IPython 生成的丰富输出。还可以直接将输出附加到输出小部件,或通过编程方式清除输出。
out = widgets.Output(layout={'border': '5px solid blue'})
out
创建小部件后,使用上下文管理器将输出直接输出到它。你可以打印文本到输出区域:
out = widgets.Output(layout={'border': '5px solid blue'})
with out:
for i in range(10):
print(i, 'Hello world!')
out
富文本的输出也可以被定向到输出区域。任何在 Jupyter 笔记本中显示得很好的东西在 Output
小部件中也会显示得很好。
from IPython.display import YouTubeVideo
out = widgets.Output(layout={'border': '5px solid blue'})
with out:
display(YouTubeVideo('eWzY2nGfkXk'))
我们甚至可以在输出小部件中显示复杂的 mimetype,比如嵌套的小部件。
out = widgets.Output(layout={'border': '5px solid blue'})
with out:
display(widgets.IntSlider())
out
我们还可以使用方便的方法 append_stdout
、append_stderr
或 append_display_data
直接将输出附加到输出小部件。
out = widgets.Output(layout={'border': '1px solid black'})
out.append_stdout('Output appended with append_stdout')
out.append_display_data(YouTubeVideo('eWzY2nGfkXk'))
out
可以通过在上下文管理器中使用 IPython.display.clear_output
来清除输出,或者直接调用小部件的 clear_output
方法。
out.clear_output()
clear_output
支持关键字参数 wait
。如果将此设置为 True
,则小部件内容不会立即清除。相反,它们将在小部件下次接收到要显示的内容时清除。这在替换输出小部件中的内容时很有用:它通过避免调用 clear_output
后小部件的不和谐的大小调整来实现更平滑的转换。
最后,我们可以使用输出小部件来捕获使用 capture
装饰器的函数产生的所有输出。
@out.capture()
def function_with_captured_output():
print('This goes into the output widget')
raise Exception('As does this')
function_with_captured_output()
out.capture
支持关键字参数 clear_output
。将此设置为 True
将在每次调用函数时清除输出小部件,以便您只能看到上次调用的输出。如果 clear_output
设置为 True
,您还可以传递一个 wait=True
参数,仅在新输出可用时清除输出。当然,您也可以在任何时候手动清除输出。
out.clear_output()
Output
小部件作为交互的基础¶
Output
小部件构成了如何实现交互和相关方法的基础。它本身也可以用来创建带有小部件和代码输出的富布局。定制交互 UI 外观的一种简单方法是使用 interactive_output
函数将控件与返回的输出小部件中捕获其输出的函数挂钩。在下一个示例中,我们垂直地堆叠控件,然后将函数的输出放在右边。
a = widgets.IntSlider(description='a')
b = widgets.IntSlider(description='b')
c = widgets.IntSlider(description='c')
def f(a, b, c):
print('{}*{}*{}={}'.format(a, b, c, a*b*c))
out = widgets.interactive_output(f, {'a': a, 'b': b, 'c': c})
widgets.HBox([widgets.VBox([a, b, c]), out])
使用输出小部件调试回调中的错误¶
在某些平台上,如 JupyterLab,小部件回调生成的输出(例如,附加在小部件特性上的 .observe
方法上的函数,或附加在按钮小部件上的 .on_click
方法上的函数)不会显示在任何地方。即使在其他平台上,也不清楚该输出应该显示在哪个单元格中。这使得调试回调函数中的错误更加困难。
访问小部件回调输出的一个有效工具是使用输出小部件的捕获方法装饰回调。然后,您可以在一个新的单元格中显示小部件,以查看回调输出。
debug_view = widgets.Output(layout={'border': '1px solid black'})
@debug_view.capture(clear_output=True)
def bad_callback(event):
print('This is about to explode')
return 1.0 / 0.0
button = widgets.Button(
description='click me to raise an exception',
layout={'width': '300px'}
)
button.on_click(bad_callback)
button
debug_view
将输出小部件与日志模块集成¶
虽然使用 .capture
装饰器可以很好地理解和调试单个回调,但它不能扩展到更大的应用程序。通常,在较大的应用程序中,可以使用 logging 模块打印有关程序状态的信息。然而,在小部件应用程序中,日志输出的去向并不清楚。
一个有用的模式是创建一个自定义 handler 程序,将日志重定向到输出小部件。然后,输出小部件可以显示在一个新的单元格中,以便在应用程序运行时监视它。
import ipywidgets as widgets
import logging
class OutputWidgetHandler(logging.Handler):
""" Custom logging handler sending logs to an output widget """
def __init__(self, *args, **kwargs):
super(OutputWidgetHandler, self).__init__(*args, **kwargs)
layout = {
'width': '100%',
'height': '160px',
'border': '1px solid black'
}
self.out = widgets.Output(layout=layout)
def emit(self, record):
""" Overload of logging.Handler method """
formatted_record = self.format(record)
new_output = {
'name': 'stdout',
'output_type': 'stream',
'text': formatted_record+'\n'
}
self.out.outputs = (new_output, ) + self.out.outputs
def show_logs(self):
""" Show the logs """
display(self.out)
def clear_logs(self):
""" Clear the current logs """
self.out.clear_output()
logger = logging.getLogger(__name__)
handler = OutputWidgetHandler()
handler.setFormatter(logging.Formatter('%(asctime)s - [%(levelname)s] %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
handler.show_logs()
handler.clear_logs()
logger.info('Starting program')
try:
logger.info('About to try something dangerous...')
1.0/0.0
except Exception as e:
logger.exception('An error occurred!')
与后台线程的输出小部件交互¶
当显示后台线程产生的输出时,Jupyter 的显示机制可能是反直觉的。后台线程的输出被打印到主线程当前正在写的单元格中。要直接看到这一点,创建一个重复打印到标准输出的线程:
import threading
import itertools
import time
def run():
for i in itertools.count(0):
time.sleep(1)
print('output from background {}'.format(i))
t = threading.Thread(target=run)
t.start()
它总是在当前活动的单元格中打印,而不是在启动后台线程的单元格中。
这可能导致输出小部件出现令人惊讶的行为。在输出小部件捕获输出的期间,在笔记本中生成的任何输出(无论线程是什么)都将进入输出小部件。
避免意外的最佳方法是永远不要在有多个线程生成输出的上下文中使用输出小部件的上下文管理器。相反,我们可以将输出小部件传递给在线程中执行的函数,并使用 append_display_data()
、append_stdout()
或 append_stderr()
方法将可显示的输出附加到输出小部件。
import threading
from IPython.display import display, HTML
import ipywidgets as widgets
import time
def thread_func(something, out):
for i in range(1, 5):
time.sleep(0.3)
out.append_stdout('{} {} {}\n'.format(i, '**'*i, something))
out.append_display_data(HTML("<em>All done!</em>"))
display('Display in main thread')
out = widgets.Output()
# Now the key: the container is displayed (while empty) in the main thread
display(out)
thread = threading.Thread(
target=thread_func,
args=("some text", out))
thread.start()
'Display in main thread'
thread.join()
output from background 0