Python笔记001-类的特殊方法

Python笔记001-类的特殊方法

如下是我学习《流畅的Python》后的我的笔记,如今拿出来和你们共享,但愿能帮到各位Python学习者。git

首次发表于: 微信公众号:科技老丁哥,ID: TechDing,敬请关注。

本篇主要知识点:

  1. 类的特殊方法(通常都在先后带有两个下划线,好比__len__和__getitem__),其存在的目的是被Python解释器调用,而不是类的对象来调用。github

  2. 对于自定义的类,通常没法体现出Python语言的核心特性,好比迭代和切片等,可是能够经过在自定义类中复写__len__和__getitem__等特殊方法来实现这些核心功能。对象调用len()方法时实际上Python解释器调用的是自定义类中的__len__,而对某个对象进行切片获取元素,或者排序时,Python解释器调用的是复写的__getitem__。bash

  3. 在自定义类中复写__getitem__至少能够得到1.类对象的切片和获取元素,2.对类对象进行迭代,能够是顺序迭代也能够是逆序迭代,3.对类对象进行从新排序,4.对类对象进行随机抽样等多种功能。微信

  4. 类的特殊方法有多达100种,做为示例,此处仅仅总结了加减乘除等的复写方法,并将全部特殊方法都整理成相关表格,便于后续复写某些特殊方法时做为参考。函数

1. 特殊方法示例

这些特殊方法长得比较奇怪,那是由于它提醒用户,不要轻易调用这些方法,由于它基本上是Python解释器专用。学习

好比下面先创建一整副纸牌,它包含有一个属性_cards,这个一个list,包含有52个Card对象。在复写了__len__和__getitem__方法以后,就能够直接获取到这个_cards属性的总长度和对其进行排序,切片来获取某几张纸牌。ui

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张,最后一张纸牌分别是什么:spa

## 经过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()..net

同理,若是没有复写__getitem__方法,在deck[0]时报错:TypeError: 'FrenchDeck' object does not support indexing3d

能够对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中特殊方法的分类与总结

TechDing_pic1.PNG

TechDing_pic2.PNG

TechDing_pic3.PNG

TechDing_pic4.PNG

TechDing_pic5.PNG

TechDing_pic6.PNG

TechDing_pic7.PNG

TechDing_pic8.PNG

TechDing_pic9.PNG

TechDing_pic10.PNG

因此,若是自定义类想实现某方面的功能,能够参考上面的表格来逐一实现便可。

首次发表于: 微信公众号:科技老丁哥,ID: TechDing,敬请关注。

本文全部代码都已经上传到个人github,欢迎下载

参考资料:

  1. 《流畅的Python》,Luciano Ramalho (做者) 安道 , 吴珂 (译者)。

  2. CSDN:Python中特殊方法的分类与总结

相关文章
相关标签/搜索