81、Python之鸭子类型:从魔法函数看对象的字符串呈现

引言

前面已经介绍了鸭子类型的概念,以及Python中支撑鸭子类型理念的“魔法函数”的体系。Python中的魔法函数分为几大类,本文我们首先从最简单的自定义类型的字符串呈现来切入,逐步理解并掌握Python中的魔法函数的完整架构。

本文的主要内容有:

1、print()函数的内部运行机制

2、对象的字符串表示的默认实现

3、通过魔法函数自定义对象字符串表示的实现

print()函数的内部运行机制

关于print()函数的使用,我们前面已经进行详细的介绍,结合print()函数的定义文档,再来简单回顾一下:

23ec9cc292719961fa01acc09e373dfc.jpeg

通常情况下,我们只需要将要输出的对象作为位置参数传递给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类,也是这样类似的实现逻辑,可以从定义中看出:

37d13d1448108fbf2d95e2118435f79d.jpeg

对象的字符串表示的默认实现

对象的字符串表示的默认实现,其实,只要写过Python代码,应该多少都接触过,我们以实际代码为例,来简单看一下:

class DaGongRen:
    def __init__(self, name, age):
        self.name = name
        self.age = age


if __name__ == '__main__':
    zs = DaGongRen('张三', 18)
    print(zs)
    print(str(zs))

执行结果:

a0402d47e139300491da98c5d3b1f0f3.jpeg

这一串看着有些奇怪的字符串,其实是一个固定的表示格式。其中隐含了模块名、类名,以及,大概是这样一个逻辑,我们通过代码直接演示一下:

class DaGongRen:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def 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())

执行结果:

5b0a1a22c8542f0b78b1c9ce82bb9cbb.jpeg

从执行结果可以看出,我们自定义的my_str()方法的输出内容,与对象的默认字符串表示的输出,是完全一样的。

其实,之所以会有这个默认实现,是由于Python中的新式类,默认都是继承自object这个基类。这种默认的字符串表示是通过object类的__repr__()方法实现的。

通过魔法函数自定义对象字符串表示的实现

从print()函数的内部作用机制,我们已然可以知道,通过魔法函数__str__()和__repr__(),可以实现自定义对象字符串表示的行为。这也是内部类型,比如容器等,等够在print()输出时,呈现出更加人性化的内容,更加便于开发者的使用、调试。

还是先通过代码来看一下吧:

class DaGongRen:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def 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())

执行结果:

05b347ae986e5e352a4eefb1bcec25de.jpeg

从执行结果可以看出,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__()实现自定义类型的对象的字符串表示的自定义实现。

感谢您的拨冗阅读,希望对您有所帮助!

363ae322bed4d4f6e90962ddf54c8777.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值