Python笔记001-类的特殊方法

系统 548 0

Python笔记001-类的特殊方法

以下是我学习《流畅的Python》后的个人笔记,现在拿出来和大家共享,希望能帮到各位Python学习者。

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

本篇主要知识点:

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

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

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

  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,欢迎下载

参考资料:

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

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


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请请扫描上面二维码支持博主1元、2元、5元等您想捐的金额吧,狠狠点击下面给点支持吧

发表我的评论
最新评论 总共0条评论