最近从学校图书馆借了一本书叫《流畅的Python》,非常喜欢它,但是不能在书上做笔记,就写到博客了。希望自己能用这两个月坚持看完它。 ——5月4日

1.1 一摞Python风格的纸牌 Page 3

import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
# 我:真的是第一个案例就来个骚操作

collections.namedtuple方法,生成了一个class,它用于构建只有少数属性但是没有方法的对象,比如数据库条目。
它相当于于(但是不等价于)下面的代码。一样的是初始化时必须提供那两个参数。不一样的是,上面方式生成的类有且只能有属性ranksuit,不能再动态绑定任何新的属性和方法了。

class Card:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

另外好奇了一下如果等号前后的Card不一样会有什么后果,以及这个赋值是不是必须的。从下面的实验中可以看出,类的名字,和指向这个类的符号是两个不同的东西,但是为了避免混淆,我们通常把他们设为一样的。

>>> kk = collections.namedtuple('Card2', ['rank', 'suit'])
>>> kk
<class '__main__.Card2'>
>>> class u:
    pass
>>> u
<class '__main__.u'>
>>> k = u
>>> k
<class '__main__.u'>
>>> u = 1
>>> k
<class '__main__.u'>
>>> u
1

1.1 一摞Python风格的纸牌 Page 4

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()
    # 我:这两行代码真的骚,但我觉得这样写只是很Pythonic,我肯定不会这么写,感觉读起来不够直观
    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]

当你实现__getitem__方法时,
1. 就可以用[n]来获取这个序列的某个值了(整数n可以为负)
2. 同时,对象就变成可迭代的了,可以用for i in obj迭代,可以用for i in reversed(obj)反向迭代
3. 可以用in运算符(即使没实现__contains__,它会按顺序做一次迭代搜索)
4. 可以用random.choice()方法来随机获取一个元素(用这个方法还需要额外实现__len__
另外,因为__getitem__方法把[]操作交给了self._cards列表,所以自动支持切片操作。
原因:position接受2种类型:intslice,而slice是切片类型。
补充(来自Page6):
当调用for i in obj时,其实用的是iter(obj),调用的__iter__方法。但是如过没有实现__iter__方法,那么它会令position从0开始递增,直到触发IndexError结束,且只能是IndexError类型的Error,否则触发错误后会引发异常。

关于特殊方法__len__ Page 6

如果是Python内置的类型,比如列表list、字符串str、字节序列bytearray等,那么CPython会抄个近路,__len__实际上会直接返回PyVarObject里的ob_size属性。PyVarObject是表示内存中长度可变的内置对象的C语言结构体。直接读取这个值比调用一个方法要快很多。
注:我们通常使用的Python是使用的CPython解释器,听说PYPY+JIT会比CPython快数十倍,但是不支持小部分包如Numpy

__str__和__repr__的区别 Page 9

Stack Overflow的知名回答
1. 默认的__repr__实现是无用的
2. __repr__的目的是尽可能详尽
3. __str__的目的是尽可能易读
4. __str__默认调用__repr__的方法
5. 务必实现__repr__,选择性实现__str__
另外,有一个技巧,如果这个类足够简单能够使得eval(repr(foo))foo是等价的,那么调试起来会非常方便。如果不能这么做,请在__repr__中提示足够多的信息,例如:"MyClass(this=%r, that=%r)" % (self.this, self.that),这里%r会调用__repr__,对应地,%s会调用__str__
%r的原因是,区分repr("3")repr(3),如下所示:

>>> repr(3)
'3'
>>> repr("3")
"'3'"
>>> print(repr(3))
3
>>> print(repr("3"))
'3'

1.2.4 自定义的布尔值 Page 10

定义__bool__方法,可以使用bool(x)
如果没实现__bool__,会检查__len__是否为0,来决定返回的布尔值,0为假,其它为真。
如果都没定义,总是返回真。

Page 11

Python内置了83个特殊方法,其中47个与运算符相关,36个与运算符无关。
表1-1:跟运算符无关的特殊方法

类别

方法名

字符串/字节序列表示形式

__repr__、__str__、__format__、__bytes__

数值转换

__abs__、__bool__、__complex__、__int__、__float__、__hash__、__index__

集合模拟

__len__、__getitem__、__setitem__、__delitem__、__contains__

迭代枚举

__iter__、__reversed__、__next__

可调用模拟

__call__

上下文管理器

__enter__、__exit__

实例创建和销毁

__new__、__init__、__del__

属性管理

__getattr__、__getattribute__、__setattr__、__delattr__、__dir__

属性描述符

__get__、__set__、__delete__

跟类相关的服务

__prepare__、__instanceheck__、__subclassheck__

表1-2:跟运算符相关的特殊方法

类别

方法名和对应的运算符

一元运算符

__neg__ -、__pos__ +、__abs__ abs()

众多比较运算符

__lt__ <、__le__ <=、__eq__ ==、__ne__ !=、__gt__ >、__ge__ <=

算数运算符

__add__ +、__sub__ -、__mul__ *、__truediv__ /、__floordiv__ //、__mod__ %、__divmod__ divmod()、__pow__ **或pow()、__round__ round()

反向算数运算符

__radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rdivmod__、__rpow__

增量赋值运算符

__iadd__、__isub__、__imul__、__itruediv__、__ifloordiv__、__imod__、__ipow__

位运算符

__invert__ ~、__lshift__ <<、__rshift__ >>、__and__ &、__or__ |、__xor__ ^

反向位运算符

__rlshift__、__rrshift__、__rand__、__rxor__、__ror__

增量赋值位运算符     

__ilshift__、__irshift__、__iand__、__ixor__、__ior__