python如何打印id_Python——花式打印对象的若⼲种⽅法
今天是Python专题的第10篇⽂章,我们来聊聊Python当中的类。
打印实例
我们先从类和对象当中最简单的打印输出开始讲起,打印⼀个实例是⼀个⾮常不起眼的应⽤,但是在实际的编程当中却⾮常重要。原因也很简单,因为我们debug的时候往往会想看下某个类当中的内容是不是符合我们的预期。但是我们直接print输出的话,只会得到⼀个地址。
我们来看⼀个例⼦:
class point:
def __init__(self, x, y):
self.x = x
self.y = y
if __name__ == "__main__":
p = point(3, 4)
print(p)
在这段代码当中我们定义了⼀个简单的类,它当中有x和y两个元素,但是如果我们直接运⾏的话,屏幕上会输出这样⼀个结果:
这个是解释器在执⾏的时候这个实例的⼀些相关信息,但是对于我们来说⼏乎没有参考意义,我们想要的是这个实例当中具体的值,⽽不是⼀个内存当中的地址。
想要实现这个功能,我们有很多⽅法,下⾯我们⼀⼀来看。
__str__⽅法
__str__⽅法⼤家应该都不陌⽣,它类似于Java当中的toString⽅法,可以根据我们的需要返回实例转化成字符串之后的结果。
⽐如,我们可以在类当中重载这个⽅法,就可以根据我们的需要输出结果了:
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)
当我们运⾏它,得到的结果会是:
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 '' % (self.x, self.y)
我们运⾏之后,会发现输出的结果还是:
x: 3, y: 4
先别着急下结论,我们再把这段代码拷贝到jupyter notebook当中,我们这次不通过打印输出,⽽通过jupyter⾃带的交互框输出交互结果,我们再来看下:
python格式化输出format奇怪,怎么结果就变成了__repr__的结果了呢?
其实这正是反应了两者的区别,如果简单理解,这两个函数都是将⼀个实例转成字符串。但是不同的是,两者的使⽤场景不同,其中
__str__更加侧重展⽰。所以当我们print输出给⽤户或者使⽤str函数进⾏类型转化的时候,Python都会默认优先调⽤__str__函数。⽽
__repr__更侧重于这个实例的报告,除了实例当中的内容之外,我们往往还会附上它的类相关的信息,因为这些内容是给开发者看的。所以当我们在交互式窗⼝输出的时候,它会优先调⽤__repr__。
这两个函数如果我们只实现了⼀个,Python在调⽤的过程当中,都会执⾏那个被我们实现的。但是这两者本⾝的应⽤场景是有区别的,只是Python为了我们⽅便做了适配。
理论上来说,对于⼀个合格的__repr__函数要能够做到:
eval(repr(obj)) == obj
也就是说我们通过__repr__输出的内容执⾏之后可以再还原得到这个实例本⾝,当然在⼀些场景下这个⾮常难以实现,所以我们退⽽求其次,保证__repr__当中输出类和对象⾜够多的信息,⽅便开发者调试和使⽤即可。
另外多说⼀句,repr是report的缩写,所以它有⼀个报告的意思在⾥⾯,⽽str就只是转化成字符串⽽已。这两者还是有⼀定区别的。
format
Python当中最常⽤的输出函数除了上⾯两个之外,还有⼀个就是format。
⽐较简单的⽤法就是通过{}代表变量,然后按照顺序依次输⼊:
除此之外,我们还可以进⼀步写明花括号⾥的变量名称,进⼀步增加可读性:
format的功能远不⽌如此,它还⽀持许多参数,类似于C语⾔当中的printf,可以通过不同的参数做到各种各样的输出。⽐如控制⼩数点后⾯保留的位数,或者是转化成百分数、科学记数法、左右对齐等功能。这⾥不⼀⼀列举了,⼤家⽤到的时候再查询即可。
我们当然可以使⽤format重新__repr__和__str__当中的逻辑,但这并不能体现它的强⼤。因为在Pyth
on当中,也为类提供了__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,可能还有些场景当中,我们希望输出。
我们针对这么多场景,如果各⾃实现不同的接⼝会⾮常⿇烦。这个时候利⽤__format__当中的这个参数,就可以⼤⼤简化这个过程,我们来看代码:
formats = {
'normal': 'x: {p.x}, y: {p.y}',
'point' : '({p.x}, {p.y})',
'prot': ''
}
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易学难精的原因。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。