流畅的python笔记 第一章:python数据模型

第零章 引言

这是我个人阅读《流畅的python》这本书的读书笔记。目前我已经把这本书看过一遍,可以说这是一本比较难啃的书,但是仔细读下来你会发现自己的python水平提高好几个档次,甚至刷新了自己对python这门语言的认识。我花了将近一个月把这本书读完,现在回过头去看自己以前写的代码,感觉确实有些不忍直视。大众对python这门语言的第一感觉就是简单容易上手,认为学两三天就可以当python程序员,事实上现在网络上也有很多python速成的资源。我当时也是通过类似的资源自学的python,并且自以为已经掌握的比较好。然而现在我推荐大家学习一下这本python进阶书,可以为你打开python新世界的大门。

不积跬步无以至千里,读完这本书我们的python功力当然也不会一步登天。通过日后一系列的博客,希望我能跟大家共同学习,在博客中存在错误也请大家帮忙指出,谢谢大家。

由于是个人的读书笔记,因此可能书中有些地方我没有完全顾及,与此同时我也会补充一些书中没有的内容。因此博客内容跟书中内容不一定完全吻合,请大家见谅。


第一章概要

第一章中,作者重点介绍了python中的魔术方法,这是python进阶的重中之重,也是本书的重中之重。在接下来许多章的内容中都应用到了魔术方法,因此本文的重点是魔术方法,顺带着会讲一下python中的一个重要模块collections模块以及类的字符串表示形式。

在这篇博客里,我将叙述以下几个知识点:魔术方法、collections模块、字符串表示形式。


魔术方法

从入门到进阶,一个很重要的点就是Python中的魔术方法,魔术方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python所调用。它们经常是两个下划线包围来命名的(比如 __init__/__new__等等),Python的魔术方法是非常强大的。

在《流畅的python》中,作者是这样描述魔术方法的:Python解释器碰到特殊的句法时,会使用特殊方法去激活一些基本的对象操作。

事实上,如果你对java或者c++有了解,那么python中的魔术方法像是java或c++中的重载。魔术方法与python系统内置的众多方法相关,比如len()、str(),以及=号、>号等等。在类中重写魔术方法,实际上是在类中对python的内置方法进行重载。

例如:为了能求得my_collection[key]的值,解释器实际上会调用my_collection.__getitem__(key)。如果在某个类中,你重写了__getitem__方法,那么下次你再使用my_collection[key]时调用的将会是你重写过的方法。

接下来我们会用一个非常简单的例子来展示如何实现__getitme____len__这两个特殊方法,通过这个例子我们也能见识到特殊方法的强大。


import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

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 __len__(self):
        return len(self._cards)

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

    

在代码1.1中,有很多值得探究的点。


首先是namedtuple,这是collections模块中定义的数据类型。

Python 拥有一些内置的数据类型,比如 str int list tuple dict 等,而 collections 模块在这些内置数据类型的基础上,提供了额外的数据类型。

collections.namedtuple

namedtuple是继承自tuple的子类,namedtuple创建一个和tuple类似的对象,但是该对象拥有自己的名称。它更像是一个带有多个数据属性的类,不过里面的数据是只读的。

namedtuple可以很方便的定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用。

from collections import namedtuple

Tpoint=namedtuple("Tpoint",["x","y"])
p=Tpoint(x=10,y=20)
print(p.x)
#10
print(p.y)
#20
print(p[0])
#10
print(p[1])
#20

#把数据变成namedtuple类:_make
t=[11,21]
p=Tpoint._make(t)
print(p)
#Tpoint(x=11, y=21)

#将字典变为namedtuple
dic={"x":30,"y":40}
dic_tuple=Tpoint(**dic)
print(dic_tuple)
#Tpoint(x=30, y=40)


collections.deque

为了高效实现插入和删除操作的双向列表

deque除了实现listappend()和pop()外,还支持appendleft()和popleft()


collections.defaultdict

使用dict时,如果引用的key不存在,就会抛出keyerror,如果希望key不存在时可以返回一个默认值,就可以用defaultdict

defaultdict的参数是一个可调用对象,当key不存在时,调用这个可调用对象。


collections.OrderedDict

这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。OrderedDictpopitem方法默认删除并返回的是字典里的最后一个元素。


回到代码1.1,我们用collections.namedtuple构建了一个简单的类来表示一张纸牌,并且可以很轻松的得到一个纸牌对象:


>>>beer_card=Card('7','diamonds')

>>>beer_card

Card(rank='7',suit='diamonds')


FrenchDeck类跟任何标准Python集合类型一样,可以用len()函数来查看一叠牌有多少张:


>>>deck=FrenchDeck()

>>>len(deck)

52


之所以可以这么做,是因为我们在类中定义了__len__方法。在使用len(deck)时,事实上是调用了deck.__len__()。而__len__是绑定方法,因此可以自动传入self参数。关于绑定方法在之后的章节中会讨论。在代码中,__len__方法返回的是len(self._cards),而在FrenchDeck类中,self._cards是一个列表,是python内置的数据类型。显然它定义了自己的__len__方法,使用过python的人都应该知道,len(list)返回的是列表长度。因此对FrenchDeck类使用len()操作如同对列表使用一样。

从一叠牌中抽取特定的一张纸牌,比如说第一张或最后一张,是很容易的:deck[0]deck[-1]。这都是由__getitem__方法提供的:


>>>deck[0]

Card(rank='2',suit='spades')

>>>deck[-1]

Card(rank='A',suit='hearts')


我们需要单独写一个方法用来随机抽取一张纸牌吗?没必要,Python已经内置了从一个序列中随机选出一个元素的函数random.choice,我们直接把它用在这一摞纸牌实例上就好:


>>>from random import choice

>>>choice(deck)

Card(rank='3',suit='hearts')

>>>choice(deck)

Card(rank='K',suit='spades')

>>>choice(deck)

Card(rank='2',suit='clubs')


为什么可以这样操作呢?关键就在于类中定义的__getitem__方法。仔细看这个方法:


def__getitem__(self,position):
 returnself._cards[position]


它所做的工作很简单,当你想使用[]符号来获取FrenchDeck类中的某个元素时,__getitem__方法会把类内_cards列表相应值返回给你,也就是说,通过__getitem__方法,对FrenchDeck类使用[]操作相当于对一个列表使用[]操作。因此,当且仅当你使用[]操作时,FrenchDeck类的实例可以看作一个列表。所有基于列表[]的方法都可以应用在该类上。例如deck[0]random.choice方法,以及接下来要说的切片和迭代。你可以这么理解:使用本例中__getitem__的定义方法,可将这个类定义为序列。

因为__getitem__方法把[]操作交给了self._cards列表,所以我们的deck类自动支持切片(slicing)操作。下面列出了查看一摞牌最上面3张和只看牌面是A的牌的操作。其中第二种操作的具体方法是,先抽出索引是12的那张牌,然后每隔13张牌拿1张:


>>>deck[:3]

[Card(rank='2',suit='spades'),Card(rank='3',suit='spades'),Card(rank='4',suit='spades')]

>>>deck[12::13]

[Card(rank='A',suit='spades'),Card(rank='A',suit='diamonds'),Card(rank='A',suit='clubs'),Card(rank='A',suit='hearts')]


另外,仅仅实现了__getitem__方法,这一摞牌就变成可迭代的了:


>>>for card in deck:

  print(card)

Card(rank='2',suit='spades')

Card(rank='3',suit='spades')

Card(rank='4',suit='spades')

...


现在已经可以体会到通过实现特殊方法(魔术方法)来利用Python数据模型的两个好处。

1.作为你的类的用户,他们不必去记住标准操作的各式名称(“怎么得到元素的总数?是.size()还是.length()还是别的什么?)。

2.可以更加方便地利用Python的标准库,比如random.choice函数。


补充一点:关于__get____getattr____getitem____getattribute__之间的差异与联系:

1.“_getattribute_只适用于所有的“.”运算符;

2.“_getitem_只适用于所有的“[]”运算符;

3.无论调用对象的什么属性,包括不存在的属性,都会首先调用_getattribute_方法;
4.只有找不到对象的属性时,才会调用_getattr_方法。

以上就是python的魔术方法,值得读者细细揣摩。





字符串表示形式

Python有一个内置的函数叫repr,它能把一个对象用字符串的形式表达出来以便辨认,这就是字符串表示形式repr就是通过__repr__这个特殊方法来得到一个对象的字符串表示形式的。如果没有实现__repr__,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是<Vectorobjectat0x10e100070>

__repr____str__的区别在于,后者是在str()函数被使用,或是在用print函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。

如果你只想实现这两个特殊方法中的一个,__repr__是更好的选择,因为如果一个对象没有__str__函数,而Python又需要调用它的时候,解释器会用__repr__作为替代。


补充:关于str.format()

str.format()会返回一个新字符串,在新的字符串中,原字符串的替换字段被format方法中的参数取代。

常用的替换方法有三种:

按照位置:


>>>{}{}”.format(“kcz”,18)

Kcz,18

>>>“{1},{0},{1}”.format(“kcz”,18)

18,kcz,18


按照字段名:


>>>”{who} is now doing {sth}”.format(who=”a”,sth=”b”)

A is now doing b


按照索引:


>>>name=[“a”,”b”,”c”]

>>>age=[“d”,”e”,”f”]

>>>“{0[0]}{0[1]}今年都{1[1]岁了}”.format(name,age)

Ab今年都e岁了





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值