热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

Python最好的品质之一是一致性。len(colleciton)而不是collection.len()写法所代表的庞大的设计思想,是形成我们通常说的“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 collectionsCard = 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 = rankself.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 22print(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])) #
print(type(deck[:])) # 仅仅实现了__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)) # ...

现在已经可以体会到通过实现特殊方法来利用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 collectionsCard = 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 = 0def __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 selfdef __next__(self):if self.start>-len(self._cards): # 这里为了有所区别,进行反向遍历self.start -= 1return 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 延伸阅读


推荐阅读
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • 开源Keras Faster RCNN模型介绍及代码结构解析
    本文介绍了开源Keras Faster RCNN模型的环境需求和代码结构,包括FasterRCNN源码解析、RPN与classifier定义、data_generators.py文件的功能以及损失计算。同时提供了该模型的开源地址和安装所需的库。 ... [详细]
  • 在本教程中,我们将看到如何使用FLASK制作第一个用于机器学习模型的RESTAPI。我们将从创建机器学习模型开始。然后,我们将看到使用Flask创建AP ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 摘要: 在测试数据中,生成中文姓名是一个常见的需求。本文介绍了使用C#编写的随机生成中文姓名的方法,并分享了相关代码。作者欢迎读者提出意见和建议。 ... [详细]
  • Python使用Pillow包生成验证码图片的方法
    本文介绍了使用Python中的Pillow包生成验证码图片的方法。通过随机生成数字和符号,并添加干扰象素,生成一幅验证码图片。需要配置好Python环境,并安装Pillow库。代码实现包括导入Pillow包和随机模块,定义随机生成字母、数字和字体颜色的函数。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • Python教学练习二Python1-12练习二一、判断季节用户输入月份,判断这个月是哪个季节?3,4,5月----春 ... [详细]
  • tcpdump 4.5.1 crash 深入分析
    tcpdump 4.5.1 crash 深入分析 ... [详细]
  • 颜色迁移(reinhard VS welsh)
    不要谈什么天分,运气,你需要的是一个截稿日,以及一个不交稿就能打爆你狗头的人,然后你就会被自己的才华吓到。------ ... [详细]
  • 很多时候在注册一些比较重要的帐号,或者使用一些比较重要的接口的时候,需要使用到随机字符串,为了方便,我们设计这个脚本需要注意 ... [详细]
  • 关于如何快速定义自己的数据集,可以参考我的前一篇文章PyTorch中快速加载自定义数据(入门)_晨曦473的博客-CSDN博客刚开始学习P ... [详细]
author-avatar
Steven
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有