输出部件:利用Jupyter的显示系统#
import ipywidgets as widgets
Output 组件可以捕获并显示标准输出、标准错误以及IPython生成的富输出内容。您还可以直接将输出内容附加到输出组件,或以编程方式清除它。
out = widgets.Output(layout={'border': '1px solid black'})
out
在部件创建后,使用上下文管理器将输出定向到它。您可以打印文本到输出区域:
with out:
for i in range(10):
print(i, 'Hello world!')
富输出也可以定向到输出区域。任何在Jupyter笔记本中显示良好的内容也会在Output部件中良好显示。
from IPython.display import YouTubeVideo
with out:
display(YouTubeVideo('eWzY2nGfkXk'))
我们甚至可以在输出组件中显示复杂的mimetypes,例如嵌套组件。
with out:
display(widgets.IntSlider())
我们还可以通过便捷方法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()
输出部件作为交互的基础#
输出部件构成了交互及相关方法实现的基础。它也可以单独用于创建包含小部件和代码输出的丰富布局。自定义交互界面外观的一种简单方法是使用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的display机制在显示后台线程产生的输出时可能会反直觉。后台线程的输出会被打印到主线程当前正在写入的任何单元格中。要直接观察到这一点,可以创建一个不断向标准输出打印的线程:
import threading
import time
import itertools
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()