最近从学校图书馆借了一本书叫《流畅的Python》,非常喜欢它,但是不能在书上做笔记,就写到博客了。希望自己能用这两个月坚持看完它。 ——5月4日
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
# 我:真的是第一个案例就来个骚操作
collections.namedtuple
方法,生成了一个class
,它用于构建只有少数属性但是没有方法的对象,比如数据库条目。
它相当于于(但是不等价于)下面的代码。一样的是初始化时必须提供那两个参数。不一样的是,上面方式生成的类有且只能有属性rank
和suit
,不能再动态绑定任何新的属性和方法了。
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
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种类型:int
和slice
,而slice
是切片类型。
补充(来自Page6):
当调用for i in obj
时,其实用的是iter(obj)
,调用的__iter__
方法。但是如过没有实现__iter__
方法,那么它会令position
从0开始递增,直到触发IndexError
结束,且只能是IndexError
类型的Error
,否则触发错误后会引发异常。
如果是Python内置的类型,比如列表list
、字符串str
、字节序列bytearray
等,那么CPython会抄个近路,__len__
实际上会直接返回PyVarObject
里的ob_size
属性。PyVarObject
是表示内存中长度可变的内置对象的C语言结构体。直接读取这个值比调用一个方法要快很多。
注:我们通常使用的Python是使用的CPython解释器,听说PYPY+JIT会比CPython快数十倍,但是不支持小部分包如Numpy
。
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'
定义__bool__
方法,可以使用bool(x)
。
如果没实现__bool__
,会检查__len__
是否为0,来决定返回的布尔值,0为假,其它为真。
如果都没定义,总是返回真。
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__ |