IPython显示对象的原理
用户代码在Kernel中的shell对象中执行之后,通过DisplayFormatter将需要显示的对象转换为一个描述显示信息的字典,并传递给客户端进行显示。目前IPython有三种客户端:控制台、QtConsole和Notebook。每种客户端都会从这个字典中提取自己能处理的信息进行显示。下面首先获得shell对象,它是InteractiveShell类的唯一实例:
from IPython.core.interactiveshell import InteractiveShell
sh = InteractiveShell.instance()
shell中的display_formatter是用来将对象转换为显示信息的DisplayFormatter对象:
sh.display_formatter
DisplayFormatter对象的formatters字典中保存所有用于显示转换的对象:
sh.display_formatter.formatters
DisplayFormatter对象的format()方法会遍历这个字典,尝试用每个Formatter对象进行转换,如果转换成功则保存到结果中。下面将"hello world"转换为输出字典,由于"hello world"只能被当作文本输出,因此结果字典中只有u'text/plain'一个键值。
disformat = sh.display_formatter.format
disformat("hello world")
display模块中有许多将对象包装为某种特殊显示的类,例如display.Javascript会将字符串当作javascript输出。下面的例子中,输出字典中有u'text/plain'和u'application/javascript'两个键,客户端根据自己的显示能力决定使用何种格式显示。由于Notebook客户端可以执行Javascript程序,因此它会选择u'application/javascript'。
from IPython import display
disformat(display.Javascript('alert("hello world")'))
上面我们通过DisplayFormatter对象的format()方法显示了对象进行显示转换之后的字典。如果我们直接让shell执行程序得到一个Javascript对象,那么这段Javascript代码就会在浏览器中运行。在Notebook中执行下面程序将会看到一个显示"hello world"的对话框。
display.Javascript('alert("hello world")')
下面我们看看如何用display.Image显示图像。当通过url关键字参数指定图像的URL时,实际上会使用一段HTML显示图像:
logourl = ""
disformat(display.Image(url=logourl))
但是如果指定embed参数为True,那么shell会将图片下载下来,将图片内容发送给客户端:
print str(disformat(display.Image(url=logourl, embed=True)))[:100]
因此我们也可以图像处理扩展库在内存中创建一副图像,并通过Image包装传递给客户端。下面的程序先创建一个表示图像的数组img,然后调用OpenCV模块cv2的blur()对图像进行模糊处理,并调用imencode()将数组压缩为PNG格式的图像,imencode()的输出是表示图像内容的字符串,我们可以通过Image类将字符串以u'image/png'格式发送给客户端进行显示。
import cv2
import numpy as np
img = np.random.randint(0,255,(300,300,3))
cv2.blur(img, (11,11), img)
r, dat = cv2.imencode(".png", img)
display.Image(dat.tostring())
Formatter对象
每种输出格式都对应一个Formatter对象,它们被保存在DisplayFormatter对象的formatters字典中,下面我们通过其中的TextFormatter,看看它们是如何工作的:
text_formatter = sh.display_formatter.formatters[u'text/plain']
每个Formatter对象在进行转换的时候,都经过如下四个步骤:
如果被转换对象的id为singleton_printers字典中的键,则使用id对应的转换函数进行转换。
如果被转换对象的类型为type_printers字典中的键,则使用类型对应的转换函数进行转换。
如果通过被转换对象的类型计算的元组(模块名,类型名)为deferred_printers字典中的键,则使用其对应的转换函数进行转换。
如果被转换对象有某种特殊的方法名,则调用此方法进行转换。
下面我们看看text_formatter的三个转换字典:
text_formatter.singleton_printers
text_formatter.type_printers
text_formatter.deferred_printers
singleton_printers字典的键是对象的id,为了知道具体的对象,我们需要通过下面的程序将id转换为对象:
import ctypes
for key in text_formatter.singleton_printers:
print ctypes.cast(ctypes.c_void_p(key), ctypes.py_object).value
在所有的Formatter对象中,只有text_formatter知道如何转换False对象,因此下面程序的输出字典中只有通过text_formatter得到的结果。
disformat(False)
如果我们希望用某种特殊的样式在Notebook中显示False对象,可以修改HTMLFormatter对象的singleton_printers字典。下面的程序先获得HTMLFormatter对象,然后在其singleton_printers字典中添加显示False的函数my_formatter:
html_formatter = sh.display_formatter.formatters[u'text/html']
def my_formatter(obj):
return 'too bad, it\'s %s' % str(obj)
html_formatter.singleton_printers[id(False)] = my_formatter
1 == 2
LatexFormatter采用LaTeX显示对象,可以用于显示数学公式,例如下面的程序在LatexFormatter的type_printers字典中添加对Fraction类的转换函数:
from fractions import Fraction
latex_formatter = sh.display_formatter.formatters[u"text/latex"]
def fraction_formatter(obj):
return '$$\\frac{%d}{%d}$$' % (obj.numerator, obj.denominator)
latex_formatter.type_printers[Fraction] = fraction_formatter
Fraction(3, 4) ** 4 / 3
deferred_printers字典和type_printers字典类似,不过它采用包含模块名和类名的元组作为键,这样可以避免在定义转换函数时载入对应的类。可以对用户的Python环境中可能不存在的类提供转换函数。
下面的程序在PNGFormatter中的deferred_printers字典中添加将特定属性的数组转换为PNG图像的函数,当转换函数返回None时,表示忽略其转换结果。
png_formatter = sh.display_formatter.formatters[u'image/png']
def ndarray_formatter(array):
if array.dtype == np.uint8 and array.ndim == 3 and array.shape[-1] == 3:
import cv2
r, dat = cv2.imencode(".png", array)
return dat.tostring()
else:
return None
png_formatter.deferred_printers[("numpy", "ndarray")] = ndarray_formatter
np.random.randint(0,255,(300,300,3)).astype(np.uint8)
对于不满足特定条件的数组,仍然使用数组的u'text/plain'转换结果进行显示:
np.arange(10)
最后,Formatter将调用特定的方法进行显示转换,例如HTMLFormatter对应的方法名为_repr_html_。因此下面的Color类可以被HTMLFormatter转换为HTML显示:
class Color(object):
def __init__(self, r, g, b):
self.rgb = (r, g, b)
def html_color(self):
return "#%x%x%x" % self.rgb
def _repr_html_(self):
c = self.html_color()
return '%s">%s' % (c, c)
Color(255, 120, 150)
相关代码
在Notebook中输入如下命令,可以查看相关对象的代码。
循环调用各个Formatter,并将结果收集到一个字典中
IPython.core.formatters.DisplayFormatter??
所有Formatter的基类
IPython.core.formatters.BaseFormatter??
将对象显示为文本Formatter
IPython.core.formatters.PlainTextFormatter??