《Fluent Python》学习笔记:第 1 章 Python数据模型

Python最好的品质之一是一致性。

len(colleciton)而不是collection.len()写法所代表的庞大的设计思想,是形成我们通常说的“Python风格”(Pythonic)的关键。

特殊方法

特殊方法的名字以两个下划线开头,以两个下划线结尾(例如__getitem__)。
比如obj[key]的背后就是__getitem__方法,为了能求得my_collection[key]的值,解释器实际上会调用my_collection.__getitem__(key)
魔术方法(magic method)是特殊方法的昵称。

1.1 一摞Python风格的纸牌

import collections
 
Card = collections.namedtuple('Card', ['rank', 'suit'])  # 等号前后的Card可不一致,一致是为避免混淆
 
class FrenchDeck:   # 一套扑克牌,不含大小王
 
    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]

__getitem__

  • 实现__getitem__方法后,该对象即可iterable,可以被当作可迭代的对象使用,具体的可迭代类型,看该方法的返回值
  • 可以使用位置索引 [n]来访问序列中的值,另外,因为 __getitem__方法把[]操作交给了 self._cards列表,所以自动支持切片操作。
  • 对象可迭代,如可以用 for i in obj迭代。当调用 for i in obj时,其实用的是 iter(obj),调用的 __iter__方法。但是如过没有实现 __iter__方法,那么它会令 position0开始递增,直到触发 IndexError结束,且只能是 IndexError类型的 Error,否则触发错误后会引发异常。
  • 可以用in运算符(即使没实现 __contains__,它会按顺序做一次迭代搜索)
  • 可以用random.choice()方法来随机获取一个元素(用这个方法还需要额外实现 __len__

namedtuple

出处:Python标准库collections
生成了一个class,用于构建只有少数属性但是没有方法的对象,比如数据库条目。它相当于于下面的代码,但有且只能有属性rank和suit,无法再动态绑定任何新的属性和方法了(元组无法修改)。这使得原始数据的含义依然能被保留,增加可读性和便捷性。

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

语法:
collections.namedtuple(typename, field_names, *, verbose=False, rename=False, module=None)

typename:实际上就是你通过namedtuple创建的一个元组的子类的类名,通过这样的方式我们可以初始化各种各样的实例化元组对象。
field_names:类似于字典的key,在这里定义的元组可以通过这样的key去获取里面对应索引位置的元素值,这样的key可以是列表,也可以是用空格、/和逗号这样的分隔符隔开的字符串。
rename:如果rename指定为True,那么你的field_names里面不能包含有非Python标识符,Python中的关键字以及重复的name,如果有,它会默认给你重命名成‘_index’的样式,这个index表示该name在field_names中的索引,例:['abc', 'def', 'ghi', 'abc']将被转换成['abc', '_1', 'ghi', '_3']

# Basic example of namedtuple
Point = collections.namedtuple('Point', ['x', 'y'])
p = Point(11, y=22)     # instantiate with positional or keyword arguments
print(Point.__doc__)    # Point(x, y)
print(p.__doc__)        # Point(x, y)
print(p)                # Point(x=11, y=22)
print(p[0], p[1])       # 11 22 可以使用索引去获取namedtuple里面的元素
 
x, y = p                # 支持分包
print(x, y)             # 11 22
 
print(p.x + p.y)        # 33 使用对应的字段名字也可以获取namedtuple里面的元素

p.x = 33                # AttributeError: can't set attribute  属性值只读,不可修改

FrenchDeck 这个类跟任何标准Python 集合类型一样,可以用len() 函数来查看一叠牌有多少张:

deck = FrenchDeck()
print(len(deck))    #52

从一叠牌中抽取特定的一张纸牌,比如说第一张或最后一张,是很容易的:deck[0]deck[-1]。这都是由__getitem__方法提供的:

deck[0]     #Card(rank='2', suit='spades')
deck[-1]    #Card(rank='A', suit='hearts')
print(type(deck[0]))  # <class '__main__.Card'>
print(type(deck[:]))  # <class 'list'>  仅仅实现了__getitem__ 方法,这一摞牌就变成可迭代的了

我们需要单独写一个方法用来随机抽取一张纸牌吗?没必要,Python 已经内置了从一个序列中随机选出一个元素的函数random.choice,我们直接把它用在这一摞纸牌实例上就好:

from random import choice
choice(deck)     #Card(rank='3', suit='hearts')
choice(deck)     #Card(rank='K', suit='spades')
choice(deck)     #Card(rank='2', suit='clubs') 

for v in deck:  # 等同于 for v in deck[:]:
    print(v)    # Card(rank='2', suit='spades') ...
    print(type(v))   # <class '__main__.Card'> ...

现在已经可以体会到通过实现特殊方法来利用Python 数据模型的两个好处。

  • 复用你写的类时,无需去记忆标准操作的各式名称(“怎么得到元素的总数?是.size()还是.length()还是别的什么?”)。
  • 可以更加方便地利用Python 的标准库,比如random.choice函数,从而不用重新发明轮子。

迭代通常是隐式的,譬如说一个集合类型没有实现__contains__方法,那么in运算符就会按顺序做一次迭代搜索。于是,in运算符可以用在我们的FrenchDeck 类上,因为它是可迭代的:

Card('Q', 'hearts') in deck  # True
Card('7', 'beasts') in deck  # False

按照黑桃最大、红桃次之、方块再次、梅花最小的规则来给扑克牌排序的函数,梅花2 的大小是0,黑桃A 是51:

suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)
def spades_high(card):
	rank_value = FrenchDeck.ranks.index(card.rank)
	return rank_value * len(suit_values) + suit_values[card.suit]
	
for card in sorted(deck, key=spades_high): 
	print(card)   # Card(rank='2', suit='clubs')  ...   Card(rank='A', suit='spades')

1.2 如何使用特殊方法

  • 特殊方法的存在是为了被Python 解释器调用的,你无需使用my_object.__len__()调用它们,应该使用len(my_object)
  • 使用len(my_object)时,如果my_object是自定义类的对象,会调用其__len__方法;如果是Python 内置的类型(如ist、str、bytearray等),CPython 会直接返回PyVarObject 里的ob_size属性,这比调用一个方法要快很多。
  • 很多时候,特殊方法的调用是隐式的,比如for i in x:这个语句,背后其实用的是iter(x),而这个函数的背后则是x.__iter__()方法。当然前提是这个方法在x 中被实现了。
  • 不要随意添加特殊方法,因为虽然现在这个名字没有被Python 内部使用,以后就不一定了。

Iterable: 有迭代能力的对象,一个类,实现了__iter__,那么就认为它有迭代能力,通常此函数必须返回一个实现了__next__的对象,如果自己实现了,你可以返回self,当然这个返回值不是必须的;
Iterator: 迭代器(当然也是Iterable),同时实现了__iter__和__next__的对象,缺少任何一个都不算是Iterator。

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()
    start = 0

    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]

    def __iter__(self):
        return self

    def __next__(self):
        if self.start>-len(self._cards):  # 这里为了有所区别,进行反向遍历
            self.start -= 1
            return self._cards[self.start]  
        else:
            raise StopIteration  # 不想迭代时,抛出StopIteration异常(for语句会捕获此异常,并结束循环)
        
doct = FrenchDeck()
for v in doct:
    print(v)    # Card(rank='A', suit='hearts')  ...

1.3 特殊方法一览

特殊方法一览

1.4 为什么len不是普通方法

如果x是一个内置类型的实例,那么len(x)的速度会非常快。背后的原因是CPython 会直接从一个C 结构体里读取对象的长度,完全不会调用任何方法。

换句话说,len 之所以不是一个普通方法,是为了让Python 自带的数据结构可以走后门,abs 也是同理。但是多亏了它是特殊方法,我们也可以把len 用于自定义数据类型。这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点,也印证了“Python 之禅”中的另外一句话:“不能让特例特殊到开始破坏既定规则。”

1.5 本章小结

通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具Python 风格的代码。

1.6 延伸阅读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python’s simplicity lets you become productive quickly, but this often means you aren’t using everything it has to offer. With this hands-on guide, you’ll learn how to write effective, idiomatic Python code by leveraging its best—and possibly most neglected—features. Author Luciano Ramalho takes you through Python’s core language features and libraries, and shows you how to make your code shorter, faster, and more readable at the same time. Many experienced programmers try to bend Python to fit patterns they learned from other languages, and never discover Python features outside of their experience. With this book, those Python programmers will thoroughly learn how to become proficient in Python 3. This book covers: Python data model: understand how special methods are the key to the consistent behavior of objects Data structures: take full advantage of built-in types, and understand the text vs bytes duality in the Unicode age Functions as objects: view Python functions as first-class objects, and understand how this affects popular design patterns Object-oriented idioms: build classes by learning about references, mutability, interfaces, operator overloading, and multiple inheritance Control flow: leverage context managers, generators, coroutines, and concurrency with the concurrent.futures and asyncio packages Metaprogramming: understand how properties, attribute descriptors, class decorators, and metaclasses work Table of Contents Part I. Prologue Chapter 1. The Python Data Model Part II. Data structures Chapter 2. An array of sequences Chapter 3. Dictionaries and sets Chapter 4. Text versus bytes Part III. Functions as objects Chapter 5. First-class functions Chapter 6. Design patterns with first-class functions Chapter 7. Function decorators and closures Part IV. Object Oriented Idioms Chapter 8. Object references, mutability and recycling Chapter 9. A Pythonic object Chapter 10. Sequence hacking, hashing and slicing Chapter 11. Interfaces: from protocols to ABCs Chapter 12. Inheritance: for good or for worse Chapter 13. Operator overloading: doing it right Part V. Control flow Chapter 14. Iterables, iterators and generators Chapter 15. Context managers and else blocks Chapter 16. Coroutines Chapter 17. Concurrency with futures Chapter 18. Concurrency with asyncio Part VI. Metaprogramming Chapter 19. Dynamic attributes and properties Chapter 20. Attribute descriptors Chapter 21. Class metaprogramming Appendix A. Support scripts

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值