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

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

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

 

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

  1. """
  2. >>> v1 = Vector2d(3, 4)
  3. 通过元接口__iter__支持拆包
  4. >>> x, y = v1
  5. >>> x, y
  6. (3.0, 4.0)
  7. 通过元接口__repr__支持字面量表示和repr函数
  8. >>> v1
  9. Vector2d(3.0, 4.0)
  10. >>> v1_clone = eval(repr(v1))
  11. >>> v1 == v1_clone
  12. True
  13. 通过元接口__str__支持print函数
  14. >>> print(v1)
  15. (3.0, 4.0)
  16. 通过元接口__bytes__支持bytes函数
  17. >>> octets = bytes(v1)
  18. >>> octets
  19. b\'d\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@\'
  20. 通过元接口__abs__支持abs函数
  21. >>> abs(v1)
  22. 5.0
  23. 通过元接口__bool__支持bool函数
  24. >>> bool(v1), bool(Vector2d(0, 0))
  25. (True, False)
  26. 通过property支持可读属性
  27. >>> v1.x, v1.y
  28. (3.0, 4.0)
  29. >>> v1.x = 123
  30. Traceback (most recent call last):
  31. ...
  32. AttributeError: can\'t set attribute
  33. 通过__hash__支持对象可散列,支持dict、set等函数
  34. >>> hash(v1)
  35. 7
  36. >>> set(v1)
  37. {3.0, 4.0}
  38. >>> {v1: \'point1\'}
  39. {Vector2d(3.0, 4.0): \'point1\'}
  40. """
  41.  
  42. from array import array
  43. import math
  44. class Vector2d:
  45. typecode = \'d\'
  46.  
  47. def __init__(self, x, y):
  48. self.__x = float(x)
  49. self.__y = float(y)
  50. @property
  51. def x(self):
  52. return self.__x
  53. @property
  54. def y(self):
  55. return self.__y
  56.  
  57. def __iter__(self):
  58. return (i for i in (self.x, self.y))
  59. def __repr__(self):
  60. class_name = type(self).__name__
  61. return \'{}({!r}, {!r})\'.format(class_name, *self)
  62. def __str__(self):
  63. return str(tuple(self))
  64. def __bytes__(self):
  65. return (bytes([ord(self.typecode)]) +
  66. bytes(array(self.typecode, self)))
  67. def __eq__(self, other):
  68. return tuple(self) == tuple(other)
  69. def __hash__(self):
  70. return hash(self.x) ^ hash(self.y)
  71. def __abs__(self):
  72. return math.hypot(self.x, self.y)
  73. def __bool__(self):
  74. return bool(abs(self))

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

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

  1. """
  2. >>> pickcard = Cards(range(52))
  3. >>> pickcard()
  4. 51
  5. >>> pickcard()
  6. 50
  7. >>> callable(pickcard)
  8. True
  9. """
  10. class Cards:
  11. def __init__(self, items):
  12. self._items = list(items)
  13. def __call__(self):
  14. return self._items.pop()

 

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

  1. """
  2. >>> poker = Poker()
  3. 支持len运算
  4. >>> len(poker)
  5. 52
  6. 支持[]运算
  7. >>> poker[0]
  8. Card(rank=\'2\', suit=\'spades\')
  9. >>> poker[-1]
  10. Card(rank=\'A\', suit=\'hearts\')
  11. 支持切片运算
  12. >>> poker[12::13]
  13. [Card(rank=\'A\', suit=\'spades\'), Card(rank=\'A\', suit=\'diamonds\'), Card(rank=\'A\', suit=\'clubs\'), Card(rank=\'A\', suit=\'hearts\')]
  14. 支持 for i in 运算
  15. >>> for card in poker: print(card) # doctest: +ELLIPSIS
  16. ...
  17. Card(rank=\'2\', suit=\'spades\')
  18. Card(rank=\'3\', suit=\'spades\')
  19. Card(rank=\'4\', suit=\'spades\')
  20. ...
  21. 支持 in 运算
  22. >>> Card(\'7\', \'hearts\') in poker
  23. True
  24. """
  25.  
  26. import collections
  27. Card = collections.namedtuple(\'Card\', [\'rank\', \'suit\'])
  28. class Poker:
  29. ranks = [str(n) for n in range(2, 11)] + list(\'JQKA\')
  30. suits = \'spades diamonds clubs hearts\'.split()
  31. def __init__(self):
  32. self._cards = [Card(rank, suit) for suit in self.suits
  33. for rank in self.ranks]
  34. def __len__(self):
  35. return len(self._cards)
  36. def __getitem__(self, position):
  37. return self._cards[position] 

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

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

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

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

  1. """
  2. >>> s = Sentence(\'hello world\')
  3. >>> s
  4. Sentence(\'hello world\')
  5. 支持迭代list运算
  6. >>> list(s)
  7. [\'hello\', \'world\']
  8. 获取迭代器
  9. >>> it = iter(s)
  10. 支持迭代器next运算
  11. >>> next(it)
  12. \'hello\'
  13. >>> next(it)
  14. \'world\'
  15. >>> next(it)
  16. Traceback (most recent call last):
  17. ...
  18. StopIteration
  19. 支持迭代for运算
  20. >>> for w in s: print(w)
  21. hello
  22. world
  23. """
  24.  
  25. import re
  26. import reprlib
  27. RE_WORD = re.compile(\'\w+\')
  28. class Sentence:
  29. def __init__(self, text):
  30. self.text = text
  31. def __repr__(self):
  32. return \'Sentence(%s)\' % reprlib.repr(self.text)
  33. def __iter__(self):
  34. word_iter = RE_WORD.finditer(self.text)
  35. return SentenceIter(word_iter)
  36.  
  37.  
  38. class SentenceIter():
  39. def __init__(self, word_iter):
  40. self.word_iter = word_iter
  41.  
  42. def __next__(self):
  43. match = next(self.word_iter)
  44. return match.group()
  45.  
  46. def __iter__(self):
  47. return self

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

  1. RE_WORD = re.compile(\'\w+\')
  2. class Sentence:
  3. def __init__(self, text):
  4. self.text = text
  5. def __repr__(self):
  6. return \'Sentence(%s)\' % reprlib.repr(self.text)
  7. def __iter__(self):
  8. for match in RE_WORD.finditer(self.text):
  9. yield match.group()  

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

  1. """
  2. ReversePrint对象的上下文管理,进入with块后,标准输出反序打印,
  3. 退出with块后,标准输出恢复正常状态。
  4. >>> with ReversePrint() as what:
  5. ... print(\'Hello world!\')
  6. !dlrow olleH
  7. >>> print(\'Hello world!\')
  8. Hello world!
  9. """
  10.  
  11. class ReversePrint:
  12. def __enter__(self):
  13. import sys
  14. self.original_write = sys.stdout.write
  15. sys.stdout.write = self.reverse_write
  16. return \'JABBERWOCKY\'
  17.  
  18. def reverse_write(self, text):
  19. self.original_write(text[::-1])
  20. def __exit__(self, exc_type, exc_value, traceback):
  21. import sys
  22. sys.stdout.write = self.original_write
  23. if exc_type is ZeroDivisionError:
  24. print(\'Please DO NOT divide by zero!\')
  25. return True

 

版权声明:本文为wahaha02原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/wahaha02/p/10254049.html