Python作为一种多范式语言,它的很多语言特性都能从其他语言上找到参照,但是Python依然形成了一套自己的“Python 风格”(Pythonic)。这种Pythonic风格完全体现在 Python 的数据模型上,而数据模型中的元接口(指那些名字以两个下划线开头,以两个下划线结尾的特殊方法,例如 __getitem__),就是编写地道的Python代码的秘密所在。这种基于元接口实现的设计模式,也叫鸭子类型(duck typing)。

鸭子类型指的是对象的类型无关紧要,只要实现了特定的接口即可。忽略对象的真正类型,转而关注对象有没有实现所需的方法、签名和语义。Python的数据模型都支持鸭子类型,鸭子类型也是地道Python编程鼓励的风格,所以如果觉得自己想创建新的抽象基类,先试着通过常规的鸭子类型来解决问题。

数据模型其实是对 Python 框架的描述,它规范了这门语言自身构建模块的接口,这些模块包括类、函数、序列、迭代器、上下文管理器等。

得益于 Python 数据模型,自定义类的行为可以像内置类型那样自然。实现如此自然的行为,靠的不是继承,而是元接口。Python给类设计了大量的元接口,具体请参看Python 语言参考手册中的“Data Model”章节。下面是一些类的元接口的展示。

"""
>>> v1 = Vector2d(3, 4) 通过元接口__iter__支持拆包
>>> x, y = v1
>>> x, y
(3.0, 4.0) 通过元接口__repr__支持字面量表示和repr函数
>>> v1
Vector2d(3.0, 4.0)
>>> v1_clone = eval(repr(v1))
>>> v1 == v1_clone
True 通过元接口__str__支持print函数
>>> print(v1)
(3.0, 4.0) 通过元接口__bytes__支持bytes函数
>>> octets = bytes(v1)
>>> octets
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' 通过元接口__abs__支持abs函数
>>> abs(v1)
5.0 通过元接口__bool__支持bool函数
>>> bool(v1), bool(Vector2d(0, 0))
(True, False) 通过property支持可读属性
>>> v1.x, v1.y
(3.0, 4.0)
>>> v1.x = 123
Traceback (most recent call last):
...
AttributeError: can't set attribute 通过__hash__支持对象可散列,支持dict、set等函数
>>> hash(v1)
7
>>> set(v1)
{3.0, 4.0}
>>> {v1: 'point1'}
{Vector2d(3.0, 4.0): 'point1'} """ from array import array
import math class Vector2d:
typecode = 'd' def __init__(self, x, y):
self.__x = float(x)
self.__y = float(y) @property
def x(self):
return self.__x @property
def y(self):
return self.__y def __iter__(self):
return (i for i in (self.x, self.y)) def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self) def __str__(self):
return str(tuple(self)) def __bytes__(self):
return (bytes([ord(self.typecode)]) +
bytes(array(self.typecode, self))) def __eq__(self, other):
return tuple(self) == tuple(other) def __hash__(self):
return hash(self.x) ^ hash(self.y) def __abs__(self):
return math.hypot(self.x, self.y) def __bool__(self):
return bool(abs(self))

函数

Python中一切皆对象,函数也不例外,而且Python中的函数还是一等对象。函数可以理解为一种可调用对象语法糖。

可调用对象的元接口是__call__。如果一个类定义了 __call__ 方法,那么它的实例可以作为函数调用。示例如下。

"""
>>> pickcard = Cards(range(52))
>>> pickcard()
51
>>> pickcard()
50
>>> callable(pickcard)
True
"""
class Cards:
def __init__(self, items):
self._items = list(items) def __call__(self):
return self._items.pop()

序列

Python 的序列数据模型的元接口很多,但是对象只需要实现 __len__ 和 __getitem__ 两个方法,就能用在绝大部分期待序列的地方,如迭代,[]运算符、切片、for i in 等操作。示例如下:

"""
>>> poker = Poker() 支持len运算
>>> len(poker)
52 支持[]运算
>>> poker[0]
Card(rank='2', suit='spades')
>>> poker[-1]
Card(rank='A', suit='hearts') 支持切片运算
>>> poker[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')] 支持 for i in 运算
>>> for card in poker: print(card) # doctest: +ELLIPSIS
...
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
... 支持 in 运算
>>> Card('7', 'hearts') in poker
True """ import collections Card = collections.namedtuple('Card', ['rank', 'suit'])
class Poker:
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] 

从测试用例上可以看出它具有序列所有特性,即便它是 object 的子类也无妨。因为它的行为像序列,那我们就可以说它是序列。

迭代

Python中,可迭代对象的元接口是__iter__。迭代器可以从可迭代的对象中获取,__iter__和__next__是它的2个主要的元接口。__iter__ 方法使对象支持迭代,__next__ 方法返回序列中的下一个元素。如果没有元素了,那么抛出 StopIteration 异常。

迭代器可以迭代,但是可迭代的对象不是迭代器,也一定不能是自身的迭代器。也就是说,可迭代的对象必须实现 __iter__ 方法,但不能实现 __next__ 方法。

只要实现__iter__接口的对象,就是迭代鸭子类型,自然就支持所有的迭代运算。示例如下:

"""
>>> s = Sentence('hello world')
>>> s
Sentence('hello world') 支持迭代list运算
>>> list(s)
['hello', 'world'] 获取迭代器
>>> it = iter(s) 支持迭代器next运算
>>> next(it)
'hello'
>>> next(it)
'world'
>>> next(it)
Traceback (most recent call last):
...
StopIteration 支持迭代for运算
>>> for w in s: print(w)
hello
world
""" import re
import reprlib RE_WORD = re.compile('\w+') class Sentence: def __init__(self, text):
self.text = text def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self):
word_iter = RE_WORD.finditer(self.text)
return SentenceIter(word_iter) class SentenceIter(): def __init__(self, word_iter):
self.word_iter = word_iter def __next__(self):
match = next(self.word_iter)
return match.group() def __iter__(self):
return self

上面这个例子中,可迭代对象Sentence通过定义迭代器SentenceIter的方式实现。更Pythonic的做法是通过生成器yield来实现。下面是一个示例,能通过上面的所有测试用例,但代码更加精简。

RE_WORD = re.compile('\w+')

class Sentence:

    def __init__(self, text):
self.text = text def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text) def __iter__(self):
for match in RE_WORD.finditer(self.text):
yield match.group()  

上下文管理器

Python的with关键字是上下文管理器语法糖,上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。with 语句开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with 语句运行结束后,会在上下文管理器对象上调用 __exit__ 方法,以此扮演 finally 子句的角色。可以看出,上下文管理器简化了 try/finally 模式。下面是一个示例。

"""
ReversePrint对象的上下文管理,进入with块后,标准输出反序打印,
退出with块后,标准输出恢复正常状态。
>>> with ReversePrint() as what:
... print('Hello world!')
!dlrow olleH
>>> print('Hello world!')
Hello world! """ class ReversePrint: def __enter__(self):
import sys
self.original_write = sys.stdout.write
sys.stdout.write = self.reverse_write
return 'JABBERWOCKY' def reverse_write(self, text):
self.original_write(text[::-1]) def __exit__(self, exc_type, exc_value, traceback):
import sys
sys.stdout.write = self.original_write
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
return True

Python数据模型及Pythonic编程的更多相关文章

  1. 如何像Python高手(Pythonista)一样编程

    最近在网上看到一篇介绍Pythonic编程的文章:Code Like a Pythonista: Idiomatic Python,其实作者在2006的PyCon会议后就写了这篇文章,写这篇文章的主要 ...

  2. 第1章 Python数据模型

    #<流畅的Python>读书笔记 # 第一部分 序幕 # 第1章 Python数据模型 # 魔术方法(magic method)是特殊方法的昵称.于是乎,特殊方法也叫双下方法(dunder ...

  3. python高级(一)—— python数据模型(特殊方法)

    本文主要内容 collections.namedtuple __getitem__ 和 __len__ __repr__和__str__ __abs__.__add__和__mul__ __bool_ ...

  4. [转]如何像Python高手(Pythonista)一样编程

    本文转自:http://xianglong.me/article/how-to-code-like-a-pythonista-idiomatic-python 最近在网上看到一篇介绍Pythonic编 ...

  5. python高级之网络编程

    python高级之网络编程 本节内容 网络通信概念 socket编程 socket模块一些方法 聊天socket实现 远程执行命令及上传文件 socketserver及其源码分析 1.网络通信概念 说 ...

  6. 【循序渐进学Python】15.网络编程

    Python 内置封装了很多常见的网络协议的库,因此Python成为了一个强大的网络编程工具,这里是对Python的网络方面编程的一个简单描述. 1. 常用的网络设计模块 在标准库中有很多网络设计相关 ...

  7. Python Decorator 和函数式编程

    看到一篇翻译不错的文章,原文链接: Python Decorator 和函数式编程

  8. Python基础:函数式编程

    一.概述 Python是一门多范式的编程语言,它同时支持过程式.面向对象和函数式的编程范式.因此,在Python中提供了很多符合 函数式编程 风格的特性和工具. 以下是对 Python中的函数式编程 ...

  9. 用 eric6 与 PyQt5 实现python的极速GUI编程(系列01)--Hello world!

    [题记] 我是一个菜鸟,这个系列是我的学习笔记. PyQt5 出来有一段时间了, PyQt5 较之 PyQt4 有一些变化,而网上流传的几乎都是 PyQt4 的教程,照搬的话大多会出错. eric6 ...

随机推荐

  1. C#中Dictionary的介绍

    关键字:C# Dictionary 字典 作者:txw1958原文:http://www.cnblogs.com/txw1958/archive/2012/11/07/csharp-dictionar ...

  2. mysql8.0卸载干净--win10

    本文介绍,在Windows10系统下,如何彻底删除卸载MySQL... 1>停止MySQL服务开始->所有应用->Windows管理工具->服务,将MySQL服务停止.2> ...

  3. Ajax数据爬取

    Ajax的基本原理 以菜鸟教程的代码为例: XMLHTTPRequest对象是JS对Ajax的底层实现: var xmlhttp; if (window.XMLHttpRequest) { // IE ...

  4. SimpleDateFormat日期格式解析

    先看一个代码示例: import java.text.SimpleDateFormat; import java.util.Date; public class test{ public static ...

  5. ASP.NET MVC - Entity Framework

    ASP.NET MVC - Entity Framework 实体关系 关系是某个实体(表)的一条记录对应于另一个实体(表)的一条或多条记录. 一对多关系 单方面的包含关系称为一对多,而一对多和一对一 ...

  6. python知识点

    if __name__ == 'main' 一个python的文件有两种使用的方法,第一是直接作为脚本执行,第二是import到其他的python脚本中被调用(模块重用)执行. 因此if __name ...

  7. python3字符串

    Python3 字符串 Python字符串运算符 + 字符串连接 a + b 输出结果: HelloPython * 重复输出字符串 a*2 输出结果:HelloHello [] 通过索引获取字符串中 ...

  8. 备考2019年6月份PMP考试-分享一些(备考)考试心得

    I'm iron man,纪念离去的钢铁侠(复联4) 话说任何一个写程序的人都不可能干一辈子(有些人例外),大部分都是要转行的. 技术转管理是一条路,技术转创业是一条路,技术干销售是一条路,技术转售前 ...

  9. [转]PostgreSQL数据类型

    第六章  数据类型 6.1概述 PostgreSQL 提供了丰富的数据类型.用户可以使用 CREATE TYPE 命令在数据库中创建新的数据类型.PostgreSQL 的数据类型被分为四种,分别是基本 ...

  10. .Net 入门资料推荐 (编辑中)

    1.首先推荐 网易云课堂上的一个付费课程:常老师带你学ASP.NET MVC ,价格199元 2. 一个.net的框架,ABP,中文介绍如下 http://www.cnblogs.com/farb/p ...