Python——详解__str__, __repr__和__format__

本文始发于我的公众号:TechFlow,原创不易,求个关注web


今天是Python专题的第10篇文章,咱们来聊聊Python当中的类。编程

打印实例

咱们先从类和对象当中最简单的打印输出开始讲起,打印一个实例是一个很是不起眼的应用,可是在实际的编程当中却很是重要。缘由也很简单,由于咱们debug的时候每每会想看下某个类当中的内容是否是符合咱们的预期。可是咱们直接print输出的话,只会获得一个地址。编辑器

咱们来看一个例子:函数

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y


if __name__ == "__main__":
    p = point(34)
    print(p)
复制代码

在这段代码当中咱们定义了一个简单的类,它当中有x和y两个元素,可是若是咱们直接运行的话,屏幕上会输出这样一个结果:spa

<__main__.point object at 0x10a18c210>
复制代码

这个是解释器在执行的时候这个实例的一些相关信息,可是对于咱们来讲几乎没有参考意义,咱们想要的是这个实例当中具体的值,而不是一个内存当中的地址。debug

想要实现这个功能,咱们有不少方法,下面咱们一一来看。设计

__str__方法

__str__方法你们应该都不陌生,它相似于Java当中的toString方法,能够根据咱们的须要返回实例转化成字符串以后的结果。调试

好比,咱们能够在类当中重载这个方法,就能够根据咱们的须要输出结果了:code

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return 'x: %s, y: %s' % (self.x, self.y)
复制代码

当咱们运行它,获得的结果会是:orm

x: 3, y: 4
复制代码

__str__和__init__, __len__不少函数同样是Python中的特殊函数,在咱们建立类的时候,系统会咱们隐式创造许多这样的特殊函数。咱们能够根据须要重载其中的一部分完成咱们想要的功能。好比若是咱们写的是一棵二叉树的类,咱们还能够在__str__函数当中进行递归遍历全部的节点,打印出完整的树来。

__repr__方法

你也许可能也据说过__repr__函数,它也能够实现根据咱们的须要自定义输出的功能。好比咱们把上面的代码改下函数名,也能够获得同样的结果。

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'x: %s, y: %s' % (self.x, self.y)
复制代码

咱们运行它,一样会获得:

x: 3, y: 4
复制代码

这是为何呢,难道__repr__和__str__是同样的吗?若是是同样的,Python的设计者干吗要保留两个彻底相同的函数呢,为何不去掉其中一个呢?

在分析缘由以前,咱们先来作一个实验,若是咱们两个函数都重载,那么当咱们输出的时候,程序执行的是哪个呢?为了作好区分,咱们把repr当中的输出的格式稍微修改一下。

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return 'x: %s, y: %s' % (self.x, self.y)

    def __repr__(self):
        return '<point x: %s, y: %s>' % (self.x, self.y)
复制代码

咱们运行以后,会发现输出的结果仍是:

x: 3, y: 4
复制代码

先别着急下结论,咱们再把这段代码拷贝到jupyter notebook当中,咱们此次不经过打印输出,而经过jupyter自带的交互框输出交互结果,咱们再来看下:

奇怪,怎么结果就变成了__repr__的结果了呢?

其实这正是反应了二者的区别,若是简单理解,这两个函数都是将一个实例转成字符串。可是不一样的是,二者的使用场景不一样,其中__str__更加侧重展现。因此当咱们print输出给用户或者使用str函数进行类型转化的时候,Python都会默认优先调用__str__函数。而__repr__更侧重于这个实例的报告,除了实例当中的内容以外,咱们每每还会附上它的类相关的信息,由于这些内容是给开发者看的。因此当咱们在交互式窗口输出的时候,它会优先调用__repr__。

理论上来讲,对于一个合格的__repr__函数要可以作到:

eval(repr(obj)) == obj
复制代码

也就是说咱们经过__repr__输出的内容执行以后能够再还原获得这个实例自己,固然在一些场景下这个很是难以实现,因此咱们退而求其次,保证__repr__当中输出类和对象足够多的信息,方便开发者调试和使用便可。

另外多说一句,repr是report的缩写,因此它有一个报告的意思在里面,而str就只是转化成字符串而已。这二者仍是有必定区别的。

format

Python当中最经常使用的输出函数除了上面两个以外,还有一个就是format

比较简单的用法就是经过{}表明变量,而后按照顺序依次输入:

除此以外,咱们还能够进一步写明花括号里的变量名称,进一步增长可读性:

format的功能远不止如此,它还支持许多参数,相似于C语言当中的printf,能够经过不一样的参数作到各类各样的输出。好比控制小数点后面保留的位数,或者是转化成百分数、科学记数法、左右对齐等功能。这里不一一列举了,你们用到的时候再查询便可。

咱们固然可使用format从新__repr__和__str__当中的逻辑,但这并不能体现它的强大。由于在Python当中,也为类提供了__format__这个特殊函数,经过重写__format__和使用format,咱们能够作到更牛的功能。

format联合__format__

咱们能够在类当中重载__format__函数,这样咱们就能够在外部直接经过format函数来调用对象,输出咱们想要的结果。

咱们来看代码:

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return 'x: %s, y: %s' % (self.x, self.y)

    def __format__(self, code):
        return 'x: {x}, y: {y}'.format(x = self.x, y = self.y)
复制代码

咱们把刚才的__repr__改为了__format__,可是须要注意一个细节,咱们多加了一个参数code,这是因为format当中支持经过参数来对处理逻辑进行配置的功能,因此咱们必需要在接口处多加一个参数。加好了之后,咱们就能够直接调用format(p)了。

到这里尚未结束,在有些场景当中,对于同一个对象咱们可能有多种输出的格式。好比点,在有些场景下咱们可能但愿输出(x, y),有时候咱们又但愿输出x: 3, y: 4,可能还有些场景当中,咱们但愿输出<x, y>。

咱们针对这么多场景,若是各自实现不一样的接口会很是麻烦。这个时候利用__format__当中的这个参数,就能够大大简化这个过程,咱们来看代码:

formats = {
    'normal''x: {p.x}, y: {p.y}',
    'point' : '({p.x}, {p.y})',
    'prot''<{p.x}, {p.y}>'
}

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return 'x: %s, y: %s' % (self.x, self.y)

    def __format__(self, code):
        return formats[code].format(p=self)
复制代码

咱们在调用的时候就能够经过参数来控制咱们究竟使用哪种格式来格式化对象了:

也就是说经过重载__format__方法,咱们把本来固定的格式化的逻辑作成了可配置的。这样大大增长了咱们使用过程中的灵活性,这种灵活性在一些问题场景当中能够大大简化和简洁咱们的代码。对于Python这门语言来讲,我我的感受实现功能只是其中很小的一个部分,把代码写得简洁美观,才是其中的大头。这也是为何不少人都说Python易学难精的缘由。

今天的文章就是这些,若是以为有所收获,请顺手点个关注或者转发吧,大家的举手之劳对我来讲很重要。

相关文章
相关标签/搜索