python 抽象接口和协议总结

通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具 Python 风格的代码。

功能协议接口
+__add__
*__mul__
str()先查找是否实现 __str__ 协议,没有查找是否实现 __repr__
bool()默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对 __bool__ 或者 __len__ 函数有自己的实现。bool(x) 的背后是调用 x.__bool__() 的结果;如果不存在 __bool__ 方法,那么 bool(x) 会尝试调用 x.__len__()。若返回 0,则 bool 会返回 False;否则返回 True
字符串 / 字节序列 表示形式__repr__、__str__、__format__、__bytes__
数值转换__abs__、__bool__、__complex__、__int__、__float__、__hash__、__index__
集合模拟__len__、__getitem__、__setitem__、__delitem__、__contains__
迭代枚举__iter__、__reversed__、__next__
可调用模拟__call__
上下文管理__enter__、__exit__
实例创建和销毁__new__、__init__、__del__
属性管理__getattr__、__getattribute__、__setattr__、__delattr__、__dir__
属性描述符__get__、__set__、__delete__
跟类相关的服务__prepare__、__instancecheck__、__subclasscheck__
一元运算符__neg__ -、__pos__ +、__abs__ abs()
众多比较运算符__lt__ <、__le__ <=、__eq__ ==、__ne__ !=、__gt__ >、__ge__ >=
算术运算符__add__ +、__sub__ -、__mul__ *、__truediv__ /、__floordiv__ //、__mod__ %、__divmod__ divmod()、__pow__ ** 或 pow()、__round__ round()
反向算术运算符__radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rdivmod__、__rpow__
增量赋值算术运算符__iadd__、__isub__、__imul__、__itruediv__、__ifloordiv__、__imod__、__ipow__
位运算符__invert__ ~、__lshift__ <<、__rshift__ >>、__and__ &、__or__ |、__xor__ ^
反向位运算符__rlshift__、__rrshift__、__rand__、__rxor__、__ror__
增量赋值位运算符__ilshift__、__irshift__、__iand__、__ixor__、__ior__

更具体细致的表格总结如下

1.基础知识
在这里插入图片描述
在这里插入图片描述

  • __init__() 方法的调用发生在实例被创建 之后 。如果要控制实际创建进程,请使用 __new__() 方法
  • 按照约定, __repr__() 方法所返回的字符串为合法的 Python表达式。
  • 在调用 print(x) 的同时也调用了 __str__() 方法。
  • 由于 bytes 类型的引入而从 Python 3 开始出现
  • 按照约定,format_spec 应当遵循 迷你语言格式规范Python 标准类库中的decimal.py 提供了自己的 __format__() 方法。

2.行为方式与迭代器类似的类

在这里插入图片描述

  • 无论何时创建迭代器都将调用 __iter__() 方法。这是用初始值对迭代器进行初始化的绝佳之处。
  • 无论何时从迭代器中获取下一个值都将调用 __next__() 方法。
  • __reversed__() 方法并不常用。它以一个现有序列为参数,并将该序列中所有元素从尾到头以逆序排列生成一个新的迭代器

3.计算属性
在这里插入图片描述
在这里插入图片描述

  • 如果某个类定义了 __getattribute__() 方法,在 每次引用属性或方法名称时 Python 都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环)
  • 如果某个类定义了 __getattr__() 方法,Python 将只在正常的位置查询属性时才会调用它。如果实例 x 定义了属性 colorx.color 将 不会 调用 x.__getattr__('color');而只会返回x.color 已定义好的值。
  • 无论何时给属性赋值,都会调用 __setattr__() 方法。
  • 无论何时删除一个属性,都将调用 __delattr__() 方法。
  • 如果定义了 __getattr__()__getattribute__() 方法,__dir__() 方法将非常有用。通常,调用 dir(x) 将只显示正常的属性和方法。如果 __getattr()__ 方法动态处理 color 属性,dir(x) 将不会将 color 列为可用属性。可通过覆盖 __dir__()方法允许将 color 列为可用属性,对于想使用你的类但却不想深入其内部的人来说,该方法非常有益。

__getattr__()__getattribute__() 方法的区别非常细微,但非常重要。可以用两个例子来解释一下

# -*- coding: utf-8 -*-


class Dynamo:

    # 属性名称以字符串的形式传入 __getattr()__ 方法。如果名
    # 称为 'color',该方法返回一个值。(在此情况下,它只是一个
    # 硬编码的字符串,但可以正常地进行某些计算并返回结果。)
    def __getattr__(self, key):
        if key == 'color':
            return 'PapayaWhip'
        else:
            # 如果属性名称未知, __getattr()__ 方法必须引发一个
            # AttributeError 例外,否则在访问未定义属性时,代码将只会
            # 默默地失败。(从技术角度而言,如果方法不引发例外或显式
            # 地返回一个值,它将返回 None ——Python 的空值。这意味着 所
            # 有 未显式定义的属性将为 None,几乎可以肯定这不是你想看到
            # 的。)
            raise AttributeError


if __name__ == '__main__':
    dyn = Dynamo()
    # dyn 实例没有名为 color 的属性,因此在提供计算值时将调用
    # __getattr__() 。  
    print(dyn.color)
    # PapayaWhip
    
    dyn.color = 'LemonChiffon'
    # 在显式地设置 dyn.color 之后,将不再为提供 dyn.color 的
    # 值而调用 __getattr__() 方法,因为 dyn.color 已在该实例中定
    # 义。
    print(dyn.color)
    # LemonChiffon


另一方面,__getattribute__() 方法是绝对的、无条件的。

# -*- coding: utf-8 -*-


class SuperDynamo:

    def __getattribute__(self, key):
        if key == 'color':
            return 'PapayaWhip'
        else:
            raise AttributeError


if __name__ == '__main__':
    dyn = SuperDynamo()

    # 在获取 dyn.color 的值时将调用 __getattribute__() 方法
    print(dyn.color)
    # PapayaWhip
    dyn.color = 'LemonChiffon'
    # 即便已经显式地设置 dyn.color,在获取 dyn.color 的值时,
    # 仍将调用 __getattribute__() 方法。如果存在
    # __getattribute__() 方法,将在每次查找属性和方法时 无条件
    # 地调用 它,哪怕在创建实例之后已经显式地设置了属性。
    print(dyn.color)
    # PapayaWhip

tips:
如果定义了类的 __getattribute__()方法,你可能还想定义一个 __setattr__()方法,并在两者之间进行协同,以跟踪属
性的值。否则,在创建实例之后所设置的值将会消失在黑洞中。

必须特别小心 __getattribute__() 方法,因为 Python查找类的方法名称时也将对其进行调用。

# -*- coding: utf-8 -*-


class Rastan:

    def __getattribute__(self, key):
        # 该类定义了一个总是引发 AttributeError 例外的
        # __getattribute__() 方法。没有属性或方法的查询会成功。  
        raise AttributeError

    def swim(self):
        return "swim"


if __name__ == '__main__':
    hero = Rastan()
    # 调用 hero.swim() 时,Python 将在 Rastan 类中查找 swim()
    # 方法。该查找将执行整个 __getattribute__() 方法,因为所有
    # 的属性和方法查找都通过 __getattribute__() 方法。在此例
    # 中, __getattribute__() 方法引发 AttributeError 例外,因此
    # 该方法查找过程将会失败,而方法调用也将失败。
    hero.swim()
    # Traceback (most recent call last):
    #   File "C:/myFiles/company_project/xbot/my_test/getattribute_test.py", line 22, in <module>
    #     hero.swim()
    #   File "C:/myFiles/company_project/xbot/my_test/getattribute_test.py", line 9, in __getattribute__
    #     raise AttributeError
    # AttributeError


4.行为方式与函数类似的类
可以让的实例变得可调用——就像函数可以调用一样——通过定义 __call__() 方法。

在这里插入图片描述
5.行为方式与序列类似的类
如果作为一系列值的容器出现——也就是说如果对某个类来说,是否“包含”某值是件有意义的事情——那么它也许应该定义
下面的特殊方法已,让它的行为方式与序列类似。

在这里插入图片描述
6.行为方式与字典类似的类
在这里插入图片描述

7.行为方式与数值类似的类

使用适当的特殊方法,可以将类的行为方式定义为与数字相仿。也就是说,可以进行相加、相减,并进行其它数学运算
在这里插入图片描述
在这里插入图片描述

之前提到的特殊方法集合采用了第一种方式:对于给定 x / y,它们为 x 提供了一种途径来表述“我知道如何将自己除以 y。”下
面的特殊方法集合采用了第二种方法:它们向 y 提供了一种途径来表述“我知道如何成为分母,并用自己去除 x
在这里插入图片描述
在这里插入图片描述
对于复合运算(原地操作符)
在这里插入图片描述
在这里插入图片描述
注意:多数情况下,并不需要原地操作方法。如果未对特定运算定义“就地”方法,Python 将会试着使用(普通)方法。例
如,为执行表达式 x /= yPython 将会:

  • 试着调用 x.__itruediv__(y)。如果该方法已经定义,并返回了 NotImplemented 之外的值,那已经大功告成了。
  • 试图调用 x.__truediv__(y)。如果该方法已定义并返回一个NotImplemented 之外的值, x 的旧值将被丢弃,并将所返回的
    值替代它,就像是进行了 x = x / y 运算。
    试图调用 y.__rtruediv__(x)。如果该方法已定义并返回了一个 NotImplemented 之外的值,x 的旧值将被丢弃,并用所返回值进行替换

因此如果想对原地运算进行优化,仅需像 __itruediv__() 方法一样定义“原地”方法。否则,基本上 Python 将会重新生成原地
运算公式,以使用常规的运算及变量赋值。

还有一些“一元”数学运算,可以对“类‐数字”对象自己执行
在这里插入图片描述
在这里插入图片描述

8.可比较的类

如果要创建自己的类,且对象之间的比较有意义,可以使用下面的特殊方法来实现比较。
在这里插入图片描述
tips:
如果定义了 __lt__() 方法但没有定义__gt__()方法,Python 将通过经交换的算子调用 __lt__() 方法。然而,Python 并不
会组合方法。例如,如果定义了 __lt__()方法和 __eq()__ 方法,并试图测试是否 x<= yPython 不会按顺序调用 __lt__()__eq()__ 。它将只调用 __le__() 方法。

9.可序列化的类

Python 支持 任意对象序列化反序列化。(多数 Python 参考资料称该过程为 “pickling” 和 “unpickling”)。该技术对与将状态保存为文件并在稍后恢复它非常有意义。所有的 内置数据类型 均已支持 pickling 。如果创建了自定义类,且希望它能够pickle,阅读 pickle 协议 了解下列特殊方法何时以及如何被调用。
在这里插入图片描述
在这里插入图片描述
重建序列化对象,Python 需要创建一个和被序列化的对象看起来一样的新对象,然后设置新对象的所有属性。__getnewargs__() 方法控制新对象的创建过程,而__setstate__() 方法控制属性值的还原方式

10.可在WITH语块中使用的类
with 语块定义了 运行时刻上下文环境;在执行 with 语句时将“进入”该上下文环境,而执行该语块中的最后一条语句将“退出
该上下文环境。
在这里插入图片描述
__exit__() 方法将总是被调用,哪怕是在 with 语块中引发了例外。实际上,如果引发了例外,该例外信息将会被传递给 __exit__() 方法。查阅 With 状态上下文环境管理器 了解更多细节。

11.真正神奇的东西

如果知道自己在干什么,你几乎可以完全控制类是如何比较的、属性如何定义,以及类的子类是何种类型。
在这里插入图片描述
在这里插入图片描述
tips: 确切掌握 Python 何时调用 __del__() 特别方法 是件难以置信的复杂事情。要想完全理解它,必须清楚 Python 如何在内存中跟踪对象。了解 Python 垃圾收集和类析构器。还可以阅读 《弱引用》、《weakref 模块》,还可以将 《gc模块》 当作补充阅读材料。

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值