流畅的python-魔术方法

第一章《流畅的python》里面的描述:Python 的魔术方法(magic method)是特殊方法的昵称。一般是用“双下划线+名称+双下划线”形式来表示,整体念起来也拗口,所以也有人把这种特殊方法名为称为“双下方法”(dunder method)。

有关于特殊方法一览,可以参考Data model

这边借用文章第一章中字牌一个小例子来说一下实现魔术方法对python数据模型好处:

## test.py
class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()
    
    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]
    def __setitem__(self, position, card):
        self._cards[position] = card

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

建立了一个字牌类,52张牌,从2到A,花色为黑桃,方块,梅花,红心

并且实现了两个特殊方法“__len__”, "__getitem__",看到这里可能没感受到魔法方法魅力

先看一下输出,用内置函数len查看牌有几张

>>>from test import FrenchDeck
>>>deck = FrenchDeck()
>>>len(deck)
52

如果是从一叠牌中抽取一张牌,或者随机抽取一张牌,或者从上到下抽4张牌,洗牌后取第一张牌

>>>deck[0]
Card(rank='2', suit='spades')

>>>import random
>>>random.choice(deck)
Card(rank='5', suit='hearts')

>>>deck[:4]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades'), Card(rank='5', suit='spades')]

>>>from random import shuffle
>>>shuffle(deck)
>>>deck[0]
Card(rank='J', suit='spades')

在这个数据模型中,使用这些特殊方法,可以观察到:

  1. FrenchDeck实现__len__方法,在调用内置函数len(deck),实际上是调用__len__()方法。
  2. FrenchDeck实现__getitem__方法,在deck[0],“[0]”中括号取0位置的时候触发__getitem__(0)方法。
  3. FrenchDeck还实现了__setitem__方法,这边比较隐晦,实际上random.shuffle洗牌过程中,是通过互相调换元素位置来实现的,掉换位置时候涉及到赋值,也就是说必须提供赋值的方法,可以试验一下当把__setitem__方法都注释掉,按照一下试一下,注释掉时候报错,注释去掉即可赋值成功。:
>>>from test import Card
>>>from test import FrenchDeck
>>>deck = FrenchDeck()
>>>deck[0]
Card(rank='2', suit='spades')
>>>A = Card('spades','A')
>>>deck[0] = A
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'FrenchDeck' object does not support item assignment

这么做的好处是什么?

  1. 一致性:python有个很重要的品质就是“一致性”,当我们设计这样一个数据模型,我们实现这样特殊方法,调用者只需要调用内置函数len,我们不需要额外新建一个内置函数比如size()这样的方法,这样调用者不需要记住那么多方法名,只需要像操作列表那样操作即可。
  2. 兼容性:并且实现这样魔术方法,很大程度上更方便调用python标准库,比如上述的random库,我们用到了随机取值和打乱洗牌的动作。

上面我们也可以调用in运算符,或者for循环:

>>> for card in deck:
...  print(card)
...
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
Card(rank='5', suit='spades')
Card(rank='6', suit='spades')
...

>>>Card('A','spades') in deck
True

虽然FrenchDeck类中没有实现__iter__方法和__contains__,但是也能使用for循环,也能用in运算符,实际上启动了后备机制,或者说python的for循环实际上是兼容两种机制的

  1. 一种是迭代器,实现了__iter__和__next__方法,它不断调用__next__方法来获取迭代器下一个值
  2. 一种是如果没有实现__iter__方法,但是实现了__getitem__方法的,会从0开始传入索引,尝试迭代对象。

in也是如此,也是按顺序迭代搜索。所以python这种机制,让没有实现__iter__方法和__contains__,但是有实现__getitem__方法也能用for语句和in运算符。

这里仿造这种方式定义一个表格的类,表格有行列,可以模仿numpy数组来取值[i,j],可以用len()函数来表示表格又多少行和列:

## test.py
class Table:
    
    def __init__(self, rows, columns):
        self._rows = int(rows)
        self._columns = int(columns)
        self._table = dict.fromkeys([(x, y) for x in range(rows)
                                        for y in range(columns)])

    def __setitem__(self, position, value):
        x, y = position
        self._table[(position)] = value

    def __len__(self):
        return self._rows, self._columns

    def __getitem__(self, position):
        x, y = position
        return self._table[(x,y)]

    def __repr__(self):
        return "Table(%s, %s)"%(self._rows,self._columns)

查看一下输出结果:

>>>from test import Table
>>>table = Table(2,2)
>>> table
Table(2, 2)

>>>table[0,0] = 1

>>> table[0,0]
1

表格输入是建立几行几列的表格,并且用一个字典将他的坐标(i,j)作为键值,要成为字典的键值,须属于可散列数据类型,并且也得实现__hash__方法。原子不可变数据类型(str、bytes 和数值类型)都是可散列类型,当然对于元组来说,要每个对象都得是散列数据类型也能成为键值,我们这边的,x,y都是数值类型,所以何在一起元组子自然也能成为键值。

这边 __getitem__ 和 __setitem__ 实现方法, 会在table[i,j]调用时候以(i,j)元组形式传进来,如__getitem__((i, j)),这里的position就是元组(i,j) 

这边也仅限于取[i,j]的坐标的值,需要能实现切片,例如table[0,:],可以在__getitem__里面另作判断,加上isinstanc(position, slice)类似的判断,另作处理。获得整行或者整列的数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喝醉的鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值