Python的特殊方法

Python 解释器碰到特殊的句法时,会使用特殊方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,以两个下划线结尾(例如__getitem__)。
譬如,当使用len(obj)时,解释器实际会调用obj.__len__
这些特殊方法名能让你自己的对象实现和支持以下的语言构架,并与之交互:

  • 迭代 (__iter__, __reversed__...)
  • 集合类 (__getitem__, __len__ ...)
  • 属性访问 (__getattr__, __setattr__...)
  • 运算符重载 (__lt__, __add__...)
  • 对象的创建和销毁 (__new__, __del__...)
  • 字符串表示形式和格式 (__repr__, __str__...)
  • 管理上下文(即with模块)(__enter__, __exit__)

下面来看一个例子,其中的类实现了2个特殊方法,__len____getitem__

from collections import namedtuple

# 使用命名元组,可以简单的构建一个对象
Card = namedtuple("Card", ["rank", "suit"])


class FrenchDeck:
    # 2-A
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    # 4种花色
    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]

用len() 函数来查看一叠牌有多少张,由__len__实现:

>>> deck = FrenchDeck()
>>> len(deck)
52

抽取特定的一张纸牌,由__getitem__实现.

>>> deck[0]
Card(rank='2', suit='spades')

因为__getitem__方法把[] 操作交给了self._cards列表,所以deck 类自动支持切片(slicing)操作。

# 查看最上面3 张
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),
Card(rank='4', suit='spades')]
# 只看牌面是A 的牌
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]

另外,仅仅实现了__getitem__方法,这一摞牌就变成可迭代的了:

>>> for card in deck: # doctest: +ELLIPSIS
... print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...

迭代通常是隐式的,譬如说一个集合类型没有实现__contains__ 方法,那么in 运算符就会按顺序做一次迭代搜索。

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

从上面的例子可以看出,通过实现__len____getitem__ 这两个特殊方法,FrenchDeck就跟一个Python 自有的序列数据类型一样,可以体现出Python 的核心语言特性(例如迭代和切片)
另外要明确的是,特殊方法的存在是为了被Python解释器调用的,自己并不需要调用它。也就是说没有obj.__len__这种写法,而应该使用len(obj).
然而如果是Python内置类型,譬如列表、字符串、字节序列等,那么CPython会抄近路,__len__实际上会直接返回PyVarObject里的ob_size属性。PyVarObject是表示内存中长度可变的内置对象的C语言结构体,直接读取这个值比调用一个方法快很多。很多时候,特殊方法的调用的是隐式的,比如for i in x:这个语句,背后其实用的是iter(x)。而这个函数的背后则是x.__iter__()方法。
另外,__init__方法外比较特殊,代码里我们可能会经常用到它,目的是在自己的子类__init__方法中调用超类的构造器。

你可能感兴趣的:(Python的特殊方法)