流畅的python学习笔记-第1章

第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__ 方法。


  1. __repr__和__str__的区别

你可能感兴趣的:(python)