IPython架构之Display

21577阅读 0评论2012-12-01 HyryStudio
分类:Python/Ruby

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
{u'application/javascript': ,
 u'application/json': ,
 u'image/jpeg': ,
 u'image/png': ,
 u'image/svg+xml': ,
 u'text/html': ,
 u'text/latex': ,
 u'text/plain': }

DisplayFormatter对象的format()方法会遍历这个字典,尝试用每个Formatter对象进行转换,如果转换成功则保存到结果中。下面将"hello world"转换为输出字典,由于"hello world"只能被当作文本输出,因此结果字典中只有u'text/plain'一个键值。

disformat = sh.display_formatter.format
disformat("hello world")
{u'text/plain': "'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")'))
{u'application/javascript': 'alert("hello world")',
 u'text/plain': ''}

上面我们通过DisplayFormatter对象的format()方法显示了对象进行显示转换之后的字典。如果我们直接让shell执行程序得到一个Javascript对象,那么这段Javascript代码就会在浏览器中运行。在Notebook中执行下面程序将会看到一个显示"hello world"的对话框。

display.Javascript('alert("hello world")')

下面我们看看如何用display.Image显示图像。当通过url关键字参数指定图像的URL时,实际上会使用一段HTML显示图像:

logourl = ""
disformat(display.Image(url=logourl))
{u'text/html': u'',
 u'text/plain': ''}

但是如果指定embed参数为True,那么shell会将图片下载下来,将图片内容发送给客户端:

print str(disformat(display.Image(url=logourl, embed=True)))[:100]
{u'image/png': '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x002\x00\x00\x00A\x08\x06\x00\x00\x00\x9

因此我们也可以图像处理扩展库在内存中创建一副图像,并通过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对象在进行转换的时候,都经过如下四个步骤:

下面我们看看text_formatter的三个转换字典:

text_formatter.singleton_printers
{136823052: ,
 136823060: ,
 136851940: ,
 136851952: ,
 136851968: }
text_formatter.type_printers
{_sre.SRE_Pattern: ,
 instancemethod: ,
 dictproxy: ,
 xrange: ,
 set: ,
 frozenset: ,
 slice: ,
 super: ,
 BaseException: ,
 builtin_function_or_method: ,
 function: ,
 classobj: ,
 dict: ,
 list: ,
 type: ,
 unicode: ,
 str: ,
 tuple: ,
 float: >,
 long: ,
 int: ,
 datetime.timedelta: ,
 datetime.datetime: }
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
None
False
True
Ellipsis
NotImplemented

在所有的Formatter对象中,只有text_formatter知道如何转换False对象,因此下面程序的输出字典中只有通过text_formatter得到的结果。

disformat(False)
{u'text/plain': '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
too bad, it's False

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
27256

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)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

最后,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)
#ff7896

相关代码

在Notebook中输入如下命令,可以查看相关对象的代码。

循环调用各个Formatter,并将结果收集到一个字典中

IPython.core.formatters.DisplayFormatter??

所有Formatter的基类

IPython.core.formatters.BaseFormatter??

将对象显示为文本Formatter

IPython.core.formatters.PlainTextFormatter?? 
上一篇:IPython Notebook架构之Kernel
下一篇:Pandas中兼并数组和字典功能的Series