引言
前面已经介绍了鸭子类型的概念,以及Python中支撑鸭子类型理念的“魔法函数”的体系。Python中的魔法函数分为几大类,本文我们首先从最简单的自定义类型的字符串呈现来切入,逐步理解并掌握Python中的魔法函数的完整架构。
本文的主要内容有:
1、print()函数的内部运行机制
2、对象的字符串表示的默认实现
3、通过魔法函数自定义对象字符串表示的实现
print()函数的内部运行机制
关于print()函数的使用,我们前面已经进行详细的介绍,结合print()函数的定义文档,再来简单回顾一下:
通常情况下,我们只需要将要输出的对象作为位置参数传递给print()函数,即可完成一个对象的打印输出。其实,结合sep、end、file参数,我们可以实现更多的打印效果:
1、sep参数,默认情况下为一个空格,当我们需要输出多个对象,并自定义分隔符时,可以指定该参数。
2、end参数,默认情况下为一个回车,也可以自行指定。
3、file参数,默认情况下其实就是sys.stdout,也可以指定自己的文件流,就变成了往文件中写入数据了,比如日志等。
以上,大概就是我们之前介绍过的关于print()函数的使用了。
但是,为什么有的对象的输出字符串包含了元素、属性等,有些对象的输出就是比较奇怪的一串字符串?要回答这个问题,就要涉及到print()函数的作用机制了,我们直接说结论:
当我们调用print(obj)函数时,Python内部的处理机制是这样的:
1、检查对象是否实现了__str__()方法,如果实现了,则调用__str__()方法,然后输出其返回值。
2、如果对象没有实现__str__()方法,则会检查对象是否实现了__repr__()方法,如果实现了,则调用__repr__()方法,然后输出其返回值。
3、如果对象两个方法都没有实现,则会使用对象的默认字符串表示形式。
需要说明的,Python内置的str类,也是这样类似的实现逻辑,可以从定义中看出:
对象的字符串表示的默认实现
对象的字符串表示的默认实现,其实,只要写过Python代码,应该多少都接触过,我们以实际代码为例,来简单看一下:
class DaGongRen:def __init__(self, name, age):self.name = nameself.age = ageif __name__ == '__main__':zs = DaGongRen('张三', 18)print(zs)print(str(zs))
执行结果:
这一串看着有些奇怪的字符串,其实是一个固定的表示格式。其中隐含了模块名、类名,以及,大概是这样一个逻辑,我们通过代码直接演示一下:
class DaGongRen:def __init__(self, name, age):self.name = nameself.age = agedef my_str(self):return f"<{self.__class__.__module__}.{self.__class__.__name__} object at {hex(id(self))}>"if __name__ == '__main__':zs = DaGongRen('张三', 18)print(zs)print(str(zs))print(zs.my_str())
执行结果:
从执行结果可以看出,我们自定义的my_str()方法的输出内容,与对象的默认字符串表示的输出,是完全一样的。
其实,之所以会有这个默认实现,是由于Python中的新式类,默认都是继承自object这个基类。这种默认的字符串表示是通过object类的__repr__()方法实现的。
通过魔法函数自定义对象字符串表示的实现
从print()函数的内部作用机制,我们已然可以知道,通过魔法函数__str__()和__repr__(),可以实现自定义对象字符串表示的行为。这也是内部类型,比如容器等,等够在print()输出时,呈现出更加人性化的内容,更加便于开发者的使用、调试。
还是先通过代码来看一下吧:
class DaGongRen:def __init__(self, name, age):self.name = nameself.age = agedef my_str(self):return f"<{self.__class__.__module__}.{self.__class__.__name__} object at {hex(id(self))}>"def __str__(self):return f"{self.__class__.__name__}(name = {self.name}, age = {self.age})"if __name__ == '__main__':zs = DaGongRen('张三', 18)print(zs)print(str(zs))print(zs.my_str())
执行结果:
从执行结果可以看出,print(obje)和str(obj),都已经自动调用了我们自定义的__str__()方法。
当然,这里我们换成__repr__()方法,也是可以的。
需要说明的是,大多数情况下,使用__str__()方法或者__repr__()方法的效果都是一样的,但是,二者还是有所区别的,主要在使用场景上:
1、__str__()方法,用于生成面向用户的可读性字符串表示,通常会被用于print()函数或者str()函数(可以理解为是调用方,虽然__repr__也是可以的,只能当做一种约定、习俗)。
2、__repr__()方法,更多地用于生成开发者的详细字符串表示,通常用于调试时使用。比如通过repr()函数调用或者直接在交互式解释器中输入对象等。
感兴趣的同学,可以自行定义__str__()和__repr__()的不同实现,然后对比print()、str()、repr()的输出内容,来看两个方法各自的触发机制。
总结
本文基于对鸭子类型和魔法函数概念的理解的基础上,首先回顾了print()函数的简单使用,介绍了print()等类似的字符串呈现的函数的作用机制,然后介绍了通过__str__()、__repr__()实现自定义类型的对象的字符串表示的自定义实现。
感谢您的拨冗阅读,希望对您有所帮助!