流畅的Python(第二版)伴读之 1.1 Python数据模型[3/3]

魔术方法全览

官方的《Python语言参考手册》中,数据模型章节列了80多个魔术方法。其中多半是为了实现算数运算符、按位运算符和比较运算符。可以参阅下表。

表1-1列出了除了二元操作符和算术运算符以外的魔术方法。本书用到了其中的大多数,包括Python新版本中的新功能,例如在3.5版中添加的异步魔术方法__anext__,3.6版本中添加的类自定义钩子__init_subclass__(此魔术方法在类被继承时调用)。

表1-1 魔术方法分类列表(操作符除外)
类别魔术方法
字符串/byte形式输出__repr__、__str__、__format__、__bytes__、__fspath__
类型转换

__bool__、__complex__、__int__、__float__、__hash__、__index__

集合操作__len__、__getitem__、__setitem__、__delitem__、__contains__
迭代__iter__、__aiter__、__next__、__anext__、__reversed__
作为函数调用/并行__call__、__await__
上下文管理__enter__、__exit__、__aexit__、__aenter__
构造和析构__init__、__del__
属性访问__getattr__、__getattribute__、__setattr__、__delattr__、__dir__
属性描述符(常用于数据代理)__get__、__set__、__delete__、__set_name__
抽象基类__instancecheck__、__subclasscheck__
元类相关__prepare__、__init_subclass__、__class_getitem__、__mro_entries__

表1-2是二元运算符和算术运算符及对应的方法。3.5版中增加的  matmul   ,   rmatmul   , __imatmul__用于支持矩阵乘法的“@”运算符,16章中详细介绍。

表1-2 运算符重载的魔术方法
类别运算符对应的魔术方法
一元算术运算-、+、abs()__neg__,__pos__,__abs__
比较运算<、<=、==、!=、>、>=__lt__,__le__,__eq__,__ne__,__gt__,__ge__
算术运算+、-、*、/、//、%、@、divmod()、round()、**、pow()__add__,__sub__,__mul__,__truediv__,__floordiv__,__mod__,__matmul__,__div__,__mod__,__round__,__pow__
逆向运算右操作数的算术运算符__radd__,__rsub__,__rmul__,__rtrue__,__div__,__rfloordiv__,__rmod__,__rmat__,__mul__,__rdivmod__,__rpow__
复合赋值+=、-=、*=、/=、//=、%=、@=、**=__iadd__,__isub__,__imul__,__itrue__,__div__,__ifloordiv__,__imod__,__imat__,__mul__,__ipow__
逆向位运算右操作数的位运算符__rand__,__ror__,__rxor__,__rlshift__,__rrshift__
位运算复合赋值&=、|=、^=、<<=、>>=__iand__,__ior__,__ixor__,__ilshift__,__irshift__

在二元运算语句中,当第一个操作数(左操作数)不识别运算符时(此运算符没有对应的魔术方法),Python将会在第二个操作数(右操作数)上调用其逆向运算符对应的魔术方法。

例如 执行 a + b时:

  1. 如果 a 有 __add__ 方法, 而且返回值不是 NotImplemented, 调用a.__add__(b), 返回结果。
  2. 如果 a 没有 __add__ 方法, 或者其__add__ 方法返回NotImplemented时,检查 b 有没有 __radd__ 方法, 如果有, 而且没有返回 NotImplemented, 调用b.__radd__(a), 返回结果。
  3. 如果 b 没有 __radd__ 方法, 或者调用 __radd__ 方法返回NotImplemented,此语句最终抛出 TypeError, 并在错误消息中指明操作数类型不支持。

复合赋值运算符是运算和赋值两步合成一步的快捷方式,例如a+=b,先计算a+b的结果,然后把结果赋值给a。16章中详细讲述逆向运算符和复合赋值运算符。

 len为什么不是方法

此标题的意思指的是:在Python中,类中不需具有被公开调用的获取长度的类方法,而统一使用内置函数len()(但是类内仍然需要实现支持len的魔术方法)。函数和方法的定义和区别应当属于OOP里的基本知识。

关于这个问题,Python的核心开发者Raymond Hettinger在2013年引用了《Python之禅》说:实用性比纯粹性更重要。“如何使用魔术方法”一节说过在内置类型上使用len(x)要快的多。CPython解释器运行len时不会调用任何方法,而是获取了底层C结构体的字段值。在str、list、memoryview等类型中查询元素个数是非常常见的操作,必须要高效。

另一方面,len不作为方法的原因是在Python数据模型中做了特殊处理。但是在自定义类型中,可以借助__len__魔术方法来支持len操作。这达到了内置对象访问的高效性和语法一致性的平衡,《Python之禅》还说过,特殊的情况也要照顾到规则。

如果把len或abs看作一元运算符或许更容易理解它的写法和用法。ABC语言是Python语言的始祖,在ABC语言中,有个类似len的运算符“#”,使用方法是“#s”(一元运算)或“x#s”(二元运算,返回x在s中出现的次数)。

章节回顾

通过魔术方法,自定义类型也可以像内置类型那样,更加Pythonic(Python风格)。

Python对象的基本规范之一是类应当提供两种字符串输出形式,一个用于调试和日志 ,另一个是给程序的使用用户看,所以有两个魔术方法来实现这个功能:__repr__、__str__。

如FrenchDeck例子中那样,模拟序列类型是用到魔术方法的典型案例。另外,数据库查询中返回的记录集,也类似序列类型。第2章介绍了内置的序列类型,12章实现了自定义的序列类型,将二维的Vector类扩展成多维。

通过运算符重载,Python提供了大量的准数值型类型,从内置类型到decimal.Decimal和fractions.Fraction,它们都支持二元算术运算符。Numpy是个经典的数学库,提供了矩阵和张量(Tensor,即广义的多维向量,机器学习中通常用到这个概念)。在16章增强Vector类的例子中会介绍逆向、复合赋值运算符等各种运算符重载。

后文中会陆续用到其他的魔术方法。


附 言
数据模型和对象模型?

Python文档中的“Python数据模型”,有很多人称为“对象模型”。Martelli、 Ravenscroft,、Holden著的《Python in a Nutshell》第三版和David Beazley著的《Python Essential Reference》第四版精彩地介绍了Python数据模型,但名称都使用了“对象模型”。维基百科上,对象模型的概念是:某种计算机语言中对象属性的集合。这也是Python数据模型的定义。此处采纳Python数据模型的说法,因为这也是Python官方参考文档对应章节的说法。

普通方法

《The Original Hacker’s Dictionary 》(黑客宝典)一书中“魔法”的定义是:无人理解,难以理解或仅为少数智者知晓。

Ruby社区也有魔术方法的说法。Python社区中很多人也用此术语。其实这些特殊的方法与魔法的本意正相反。Python和Ruby 为他们的用户提供了丰富的元对象协议,该协议有完整的文档记录,使像你我这样的麻瓜(《哈利波特》中对不懂魔法的普通人的称呼)能够像核心开发人员一样使用和扩展许多功能。

相比之下, Go语言中也内置了一些具有特殊功能的对象,但是无法在自定义类型中实现它们。例如,Go语言的数组、字符串和map类型支持使用 “[ ]” 下标访问元素,如 a[i]。但是,全新定义的自定义类型(即不是继承了上述内置类型)没有办法实现下标访问。更糟糕的是,Go 没有用户自定义可迭代接口或迭代器对象的概念,因此它的 for/range 语法仅限于支持五种内置了“魔法方法”的内置类型,包括数组、字符串和映射。

也许在未来,Go 的设计者会增强它的元对象协议。但目前,功能上比 Python 或 Ruby要有限得多。

元对象

元对象对于理解Python数据模型非常有用,其他语言也有类似概念。《The Art of the Metaobject Protocol (AMOP)》,即《元对象协议的艺术》一书可以帮助学习元对象,“元对象”一章说元对象是语言本身的一部分。而“协议” 相当于计算机学科常说的接口。因此,元对象协议就是对象模型的同义词,他们都指的是构成语言核心体系的API。

在Java、C#里常说的反射、自省,在Python里面实现起来异常简单,这是动态语言赋予的先天优势,其基础原理就是元编程。所谓元编程,可以理解为编程语言给程序员提供了一种机制,可以对类和实例(即对象)内部的属性和方法在运行时进行创建、修改或销毁。

丰富的元对象协议可以扩展语言,支持新的编程规范。Gregor Kiczales 是 《元对象协议的艺术》的第一作者,后来成为面向切面编程的先驱,也是 AspectJ 的最初作者,AspectJ 是实现面向切面编程规范的 Java 扩展。在像 Python 这样的动态语言中,面向切面编程要容易得多,一些框架可以做到这一点。zope.interface是其中的重要一员,它是构建 Plone内容管理系统(一个开源的内容管理系统应用程序)的一部分。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值