以下是我学习《流畅的 Python》后的个人笔记, 现在拿出来和大家共享, 希望能帮到各位 Python 学习者.
首次发表于: 微信公众号: 科技老丁哥, ID: TechDing, 敬请关注.
本篇主要知识点:
类的特殊方法 (一般都在前后带有两个下划线, 比如__len__和__getitem__), 其存在的目的是被 Python 解释器调用, 而不是类的对象来调用.
对于自定义的类, 一般无法体现出 Python 语言的核心特性, 比如迭代和切片等, 但是可以通过在自定义类中复写__len__和__getitem__等特殊方法来实现这些核心功能. 对象调用 len() 方法时实际上 Python 解释器调用的是自定义类中的__len__, 而对某个对象进行切片获取元素, 或者排序时, Python 解释器调用的是复写的__getitem__.
在自定义类中复写__getitem__至少可以获得 1. 类对象的切片和获取元素, 2. 对类对象进行迭代, 可以是顺序迭代也可以是逆序迭代, 3. 对类对象进行重新排序, 4. 对类对象进行随机抽样等多种功能.
类的特殊方法有多达 100 种, 作为示例, 此处仅仅总结了加减乘除等的复写方法, 并将所有特殊方法都整理成相关表格, 便于后续复写某些特殊方法时作为参考.
1. 特殊方法示例
这些特殊方法长得比较奇怪, 那是因为它提醒用户, 不要轻易调用这些方法, 因为它基本上是 Python 解释器专用.
比如下面先建立一整副纸牌, 它包含有一个属性_cards, 这个一个 list, 包含有 52 个 Card 对象. 在复写了__len__和__getitem__方法之后, 就可以直接获取到这个_cards 属性的总长度和对其进行排序, 切片来获取某几张纸牌.
- from collections import namedtuple
- Card=namedtuple('Card', ['rank', 'suit']) # 单张纸牌类
- class FrenchDeck: # 一副纸牌, 包含 4 种花色, 每种 13 种数字
- ranks = [str(n) for n in range(2, 11)] + list('JQKA') # 13 种数字
- suits = 'spades diamonds clubs hearts'.split() # 四种不同花色: 黑桃, 方块, 梅花, 红桃
- def __init__(self):
- self._cards = [Card(rank, suit) for suit in self.suits
- for rank in self.ranks]
- # _cards 属性是所有纸牌的集合, 一个 list, 包含 4 种花色共 52 张牌
- def __len__(self):
- # 类 FrechDeck 的特殊方法: 会计算所有纸牌的张数, 即 52
- return len(self._cards)
- def __getitem__(self, position):
- # 类 FrechDeck 的特殊方法: 从 52 张牌获取第 position 张牌
- return self._cards[position]
构建一副纸牌后, 我们想知道里面有多少张牌, 或者想知道第 1 张, 第 10 张, 最后一张纸牌分别是什么:
- ## 通过 len() 获取 deck 中一共有多少张牌
- deck=FrenchDeck()
- print(len(deck)) # 52
- ## 通过切片方法获取某个位置的牌
- print(deck[0]) # Card(rank='2', suit='spades')
- print(deck[10]) # Card(rank='Q', suit='spades')
- print(deck[-1]) # Card(rank='A', suit='hearts')
如果没有复写__len__函数, 在 len(deck) 时会报错: TypeError: object of type 'FrenchDeck' has no len().
同理, 如果没有复写__getitem__方法, 在 deck[0] 时报错: TypeError: 'FrenchDeck' object does not support indexing
可以对 deck 进行索引来获取某一个位置的纸牌, 那么也就可以通过切片来获取一个批次, 符合一定条件的纸牌, 比如下面获取最后三张牌和牌面全是 A 的牌.
- print(deck[-3:]) # 获取最后三张牌
- print(deck[12::13]) # 获取全是 A 的牌, 间隔型切片
同理, 复写了__getitem__方法后, 就可以对 deck 对象进行迭代, 不管是顺序迭代还是逆序迭代都可以.
- # 顺序迭代
- for card in deck[:10]: # 只打印最前面的 10 张牌
- print(card)
- # 逆序迭代
- for card in reversed(deck[-10:]): # 只打印最后面的 10 张牌
- print(card)
还可以判断某张牌是否存在于该副牌内:
- ## 判断某张牌是否存在于该副牌内:
- print(Card('Q', 'hearts') in deck) # True
- print(Card('Z', 'clubs') in deck) # False
有了__getitem__方法, 就可以对所有纸牌进行排序操作, 此处我们排序的规定是: 梅花 2 最小, 方块 2 次之, 红桃 2, 黑桃 2, 梅花 3... 这种形式, 所以要自定义一个排序方法, 这个方法返回一个整数, 代表每张牌的大小, 那么最小的梅花 2 就是 0, 最大的黑桃 A 就是 51.
- ## 使用自定义方法对所有纸牌进行重新排序
- suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) # 将花色转换为数值
- def spades_high(card):
- rank_value = FrenchDeck.ranks.index(card.rank) # 获取某张牌的数字
- return rank_value * len(suit_values) + suit_values[card.suit]
- # 将某张牌的数字和花色换成整数返回
- for card in sorted(deck, key=spades_high):
- # 按照自定义方法 spades_high 得到的整数进行排序
- print(card)
小结一下: 自定义类通过复写__getitem__方法, 可以获得多种原来不存在的功能: 比如切片功能, 索引功能, 还可以判断某个对象是否存在于类对象列表中, 还可以对类对象进行迭代操作和排序操作.
2. 特殊方法的使用
对于自定义的类型, 使用这些特殊方法可以使得他们的表现具有某些 Python 核心功能, 在你对这些自定义类型进行操作时, 比如 len(deck) 时, Python 解释器会去找 deck 类的__len__方法.
而 Python 内置的类型, 比如 list, str, tuple 等, Python 解释器则直接抄近路, 会直接返回 PyVarObject 里的 Ob_size 属性, 这是一个长度可变的 C 语言结构体, 所以速度要比调用__len__方法快得多.
你自己对这些特殊方法的使用频率会很低, 因为都是 Python 解释器自动调用, 只有类定义时的__init__方法例外.
2.1 自定义加减乘除功能
- class Person:
- def __init__(self,name,age,score):
- self.name=name
- self.age=age
- self.score=score
- def __add__(self,other):
- # 此处仅仅是将得分相加
- return self.score+other.score
- def __sub__(self,other):
- # 此处将得分相减
- return self.score-other.score
- def __mul__(self,other):
- # 此处将两个的年龄相乘
- return self.age*other.age
- def __truediv__(self,other):
- # 将两个的得分相除
- return self.score/other.score
这个自定义类 Person 复写了加减乘除方法, 根据需要对里面的属性进行算术操作, 那么就可以用符号 +,-,*,/ 等进行操作, 比如:
- P1=Person('Jack',20,90)
- P2=Person('Rose',18,60)
- print(P1+P2) # 150
- print(P1-P2) # 30
- print(P1*P2) # 360
- print(P1/P2) # 1.5
2.2 自定义 print 后的形式
还有一个非常常用的特殊函数:__repr__, 它决定了 print 被直接调用后结果表现形式.
- class Person:
- def __init__(self,name,age,score):
- self.name=name
- self.age=age
- self.score=score
- def __repr__(self):
- return 'Person(name={},age={},score={})'.format(self.name,self.age,self.score)
- P1=Person('Jack',20,90)
- print(P1) # Person(name=Jack,age=20,score=90)
如果没有复写__repr__, 在用 print(P1) 时, 会得到内存地址信息, 人眼无法判断出具体内容, 复写之后, 就可以按照我们想要的形式直接 print.
3. 特殊方法汇总
Python 内置的特殊方法有将近一百种, 其中有很多是实现算数运算, 位运算和比较操作的, 下面将这些方法的意义总结如下:
下面的整理于: CSDN: Python 中特殊方法的分类与总结
所以, 如果自定义类想实现某方面的功能, 可以参考上面的表格来逐一实现即可.
首次发表于: 微信公众号: 科技老丁哥, ID: TechDing, 敬请关注.
本文所有代码都已经上传到我的 GitHub https://github.com/RayDean/PythonNotes , 欢迎下载
参考资料:
《流畅的 Python》,Luciano Ramalho (作者) 安道 , 吴珂 (译者).
CSDN:Python 中特殊方法的分类与总结
来源: https://www.cnblogs.com/RayDean/p/10976120.html