第1章 序幕
[toc]
这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法。比如__len__,__getitem__. 并用一个纸牌的程序来讲解了这些方法
tuple和nametuple的区别
Nametuple是类似于元组的数据类型。 除了能够用索引来访问数据,还支持用方便的属性名来访问数据。
传统的元组访问如下。
tup1 = ('abc', 'def', 'ghi')print(tup1[1])对每个元素的访问都必须通过索引来找到。这种找法很不直观
使用nametuple来构造:
import collectionstup2 = collections.namedtuple('tuple2', ['name', 'age', 'height'])t1 = tup2('zhf', '33', '175')print(t1)print(t1.age)print(t1.height)print(t1.name)得到结果如下,namedtupel中tuple2是类型名,name,age,height是属性名字从上面的访问可以看到,直接用t1.age的方法访问更加直观。
当然也可以用索引比如t1[0]的方法来访问
程序执行返回结果如下:
tuple2(name='zhf', age='33', height='175')33175zhfnamedtupe1也支持迭代访问:
for t in t1: print(t)**`和元组一样,namedtupel中的元素也是不可变更的。`**
如果执行t1.age+=1。将会提示无法设置元素
t1.age += 1AttributeError: can't set attribute下面来看下书中的纸牌例子,代码如下
from collections import namedtupleCard = namedtuple('Card', ['rank', 'suit'])class FrenchDeck(object): 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]if __name__ == '__main__': deck = FrenchDeck() #如果类中有一个__len__方法, len函数首先就会调用类里面的方法 print(len(deck)) print(deck[1])首先定义了的纸牌元组Card, rank代表纸牌数字,suit代表纸牌花色。 然后在FrenchDeck首先定义了ranks和suit的具体值。 在`__init__`中对self._cards进行初始化。 `__len__`反馈self._cards的长度。`__getitem__`反馈具体的纸牌值。
结果如下: 纸牌的长度为52,其中deck[1]为Card(rank=’3’,suit=’spades’)
可以看到len(deck)其实调用的是__len__方法。
deck[1]调用的是__getitem__由于有了__getitem__方法,
还可以进行迭代访问,如下:
for d in deck: print (d)既然是可迭代的,那么我们可以模拟随机发牌的机制。
from random import choice print (choice(deck))随机抽取,使用random.choice标准库
要看看choice底层实现原理,搞明白机制
接下来看另外一个例子,关于向量运算的。 比如有向量1 vector1(1,2),向量2 vector2(3,4)。 那么vector1+vector2的结果应该是(4,6)。
Vector1和vector2都是向量,如何实现运算呢。方法是__add__,__mul__
代码如下:
from math import hypotclass vector(object): def __init__(self, x=0, y=0): self.x = x self.y = y def __repr__(self): return 'Vector(%r,%r)' % (self.x, self.y) def __abs__(self): return hypot(self.x, self.y) def __bool__(self): return bool(abs(self)) def __add__(self, other): x = self.x + other.x y = self.y + other.y return vector(x, y) def __mul__(self, scalar): return vector(self.x * scalar, self.y * scalar)if __name__ == '__main__': v1 = vector(1, 2) v2 = vector(2, 3) print(v1 + v2) print(abs(v1)) print(v1 * 3)返回结果:
Vector(3,5)2.23606797749979Vector(3,6)在这里__add__,_ _mul_ __,__abs__分别实现了向量加法,乘法,以及求模的运算。
值得一提的是__repr__的方法。
这个方法是在需要打印对象的时候调用。
例如print vector(1,2)的时候得到vector(1,2).
否则就是表示对象的字符串:<Vector object at 0x0000>.
这个__repr__和__str__的作用是类似的
这里要知道__repr__和__str__的区别在哪里 1
## `__repr__和__str__`的区别 主要区别如下:
区别1
如果_
_str__ 存在会首先调用__str__如果__str__ 不存在会调用__repr__
测试代码如下:
class DemoClass(object): def __repr__(self): return 'DemoClass repr'
def __str__(self): return 'DemoClass str'
demo = DemoClass()print(demo) #返回是:DemoClass strclass DemoClass(object): def __repr__(self): return 'DemoClass repr'
demo = DemoClass()print(demo) # DemoClass repr区别2
class DemoClass(object): def __init__(self, name): self.name = name
def __repr__(self): # %r 字符串 (采用repr()的显示) return "repr My name is %r" % (self.name)
def __str__(self): return "str My name is %s" % (self.name)
demo = DemoClass("tony")print(demo) # 返回"str My name is tony"
# 使用repr(obj)的时候,会自动调用__repr__函数print(repr(demo)) # 返回"repr My name is tony"
demo1 = DemoClass("'tony1")print(demo1) # str My name is 'tony1print(repr(demo1)) # repr My name is "'tony1"可以看到有一些区别的
__repr__所返回的字符串应该准确、无歧义,并且尽可能表达出如何用代码创建出这个被打印的对象。
__repr__和__str__的区别在于,后者是在str()函数被使用,或是在用print函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。
再比如下面一个例子:
class DemoClass(object): def __init__(self, name): self.name = name
def say(self): print(self.name)
def __repr__(self): return "DemoClass('jacky')"
def __str__(self): return "str My name is %s" % (self.name)
demo = DemoClass("tony")demo.say() # tony
demo_repr = type(repr(demo))print(demo_repr) # <class 'str'>
demo_eval = type(eval(repr(demo)))print(demo_eval) # <class '__main__.DemoClass'>
# 用于重建对象,如果eval(repr(obj))会得到一个对象的拷贝。study = eval(repr(demo))study.say() # jacky区别3
这里额外比较repr和str函数的区别
在 Python 中要将某一类型的变量或者常量转换为字符串对象通常有两种方法,即str() 或者 repr()
>>> a = 10>>> type(str(a))<class 'str'>>>> type(repr(a))<class 'str'>但是这二者之间有什么区别呢? 因为提供两个功能完全相同的内建函数是没有意义的。
先看一个例子。
>>> print(str('123'))123>>> print(str(123))123>>> print(repr('123'))'123'>>> print(repr(123))123从例子中不难发现,当我们把一个字符串传给 str() 函数再打印到终端的时候,输出的字符不带引号。
而将一个字符串传给 repr() 函数再打印到终端的时候,输出的字符带有引号。
造成这两种输出形式不同的原因在于:
print 语句结合 str() 函数实际上是调用了对象的__str__方法来输出结果。
而 print 结合 repr() 实际上是调用对象的 __repr__ 方法输出结果。
下例中我们用 str 对象直接调用这两个方法,输出结果的形式与前一个例子保持一致。
>>> print('123'.__repr__())'123'>>>> print('123'.__str__())123不过这个例子可能还是无法很好表达到底 str() 与 repr() 各有什么意义 我们再来看一个例子。
>>> from datetime import datetime>>> now = datetime.now()>>> print(str(now))2017-04-22 15:41:33.012917>>> print(repr(now))datetime.datetime(2017, 4, 22, 15, 41, 33, 12917)通过 str() 的输出结果我们能很好地知道 now 实例的内容, 但是却丢失了 now 实例的数据类型信息。
而通过 repr() 的输出结果我们不仅能获得 now 实例的内容, 还能知道 now 是 datetime.datetime 对象的实例。
再比如:
string = "Hello\tWill\n"print(str(string))print(repr(string))结果:
Hello Will'Hello\tWill\n'再比如:
a = "哈哈"
print(str(a))print(repr(a))
# 返回结果"""哈哈'哈哈'"""因此 str() 与 repr() 的不同在于:
- str() 的输出追求可读性,输出格式要便于理解,适合用于输出内容到用户终端。
- repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用。
另外如果想要自定义类的实例能够被 str() 和 repr() 所调用,
那么就需要在自定义类中重载 __str__ 和 __repr__ 方法。
Footnotes
-
__repr__和__str__的区别 ↩