第1章 序幕
[toc]
这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法。比如__len__
,__getitem__
. 并用一个纸牌的程序来讲解了这些方法
tuple和nametuple的区别
Nametuple是类似于元组的数据类型。
除了能够用索引来访问数据,还支持用方便的属性名来访问数据。
传统的元组访问如下。
tup1 = ('abc', 'def', 'ghi')
print(tup1[1])
对每个元素的访问都必须通过索引来找到。这种找法很不直观
使用nametuple来构造:
import collections
tup2 = 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')
33
175
zhf
namedtupe1也支持迭代访问:
for t in t1:
print(t)
和元组一样,namedtupel中的元素也是不可变更的。
如果执行t1.age+=1。将会提示无法设置元素
t1.age += 1
AttributeError: can't set attribute
下面来看下书中的纸牌例子,代码如下
from collections import namedtuple
Card = 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 hypot
class 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.23606797749979
Vector(3,6)
在这里__add__,_ _mul_ __,__abs__
分别实现了向量加法,乘法,以及求模的运算。
值得一提的是__repr__
的方法。
这个方法是在需要打印对象的时候调用。
例如print vector(1,2)的时候得到vector(1,2).
否则就是表示对象的字符串:
这个__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 str
class 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 'tony1
print(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) #
demo_eval = type(eval(repr(demo)))
print(demo_eval) #
# 用于重建对象,如果eval(repr(obj))会得到一个对象的拷贝。
study = eval(repr(demo))
study.say() # jacky
区别3
这里额外比较repr和str函数的区别
在 Python 中要将某一类型的变量或者常量转换为字符串对象通常有两种方法,即str() 或者 repr()
>>> a = 10
>>> type(str(a))
>>> type(repr(a))
但是这二者之间有什么区别呢?
因为提供两个功能完全相同的内建函数是没有意义的。
先看一个例子。
>>> 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__
方法。
-
__repr__和__str__
的区别 ↩