Skip to content

自定义序列

有很多方法可以让你的 Python 类表现得像内置序列(dicttupleliststr等)。这些是迄今为止我在 Python 中最喜欢的魔法方法,因为它们为您提供了荒谬的控制程度,以及它们神奇地使整个全局函数数组在您的类实例上完美运行的方式。但在我们开始讨论好东西之前,先简单介绍一下需求。

要求

现在我们正在讨论在 Python 中创建自己的序列,是时候讨论 协议 了。协议有点类似于其他语言中的接口,因为它们为您提供了一组您必须定义的方法。然而,在 Python 中,协议是完全非正式的,不需要显式声明来实现。相反,它们更像是指导方针。

为什么我们现在谈论协议?因为在 Python 中实现自定义容器类型涉及使用其中一些协议。首先,有定义不可变容器的协议:要创建不可变容器,您只需要定义__len____getitem__(稍后会详细介绍)。可变容器协议需要不可变容器所需的一切,__setitem__加上__delitem__. 最后,如果你希望你的对象是可迭代的,你必须定义__iter__ ,它返回一个迭代器。该迭代器必须符合迭代器协议,该协议要求迭代器具有称为__iter__(返回自身)和next.

容器背后的魔力

以下是容器使用的神奇方法:

  • __len__(self)

    返回容器的长度。不可变和可变容器协议的一部分。

  • __getitem__(self, key)

    使用符号 定义访问项目时的行为self[key]。这也是可变和不可变容器协议的一部分。它还应该引发适当的异常:TypeError 如果键的类型错误并且键KeyError没有对应的值。

  • __setitem__(self, key, value)

    使用符号 定义分配项目时的行为self[nkey] = value。这是可变容器协议的一部分。KeyError同样,你应该TypeError在适当的时候加注。

  • __delitem__(self, key)

    定义删除项目时的行为(例如del self[key])。这只是可变容器协议的一部分。使用无效密钥时,您必须引发适当的异常。

  • __iter__(self)

    应该返回容器的迭代器。迭代器在许多上下文中返回,最显着的是通过iter()内置函数以及使用 form 循环容器时for x in container:。迭代器是它们自己的对象,它们还必须定义一个__iter__返回的方法self

  • __reversed__(self)

    调用以实现reversed()内置函数的行为。应该返回序列的反转版本。仅当序列类是有序的时才实现这一点,例如列表或元组。

  • __contains__(self, item)

    __contains__ in使用和定义成员资格测试的行为 not in 。你问,为什么这不是序列协议的一部分?因为当 __contains__ 没有定义,Python 只是遍历序列并 True 在遇到它正在寻找的项目时返回。

  • __missing__(self, key)

    __missing__用于 的子类 dict 。它定义了每当访问字典中不存在的键时的行为(例如,如果我有一本字典 d 并说 d["george"] 何时 "george" 不是字典中的键,d.__missing__("george")将被调用)。

一个例子

对于我们的示例,让我们看一个列表,该列表实现了一些您可能习惯于从其他语言(例如 Haskell)中使用的功能结构。

python
class FunctionalList:
    """A class wrapping a list with some extra functional magic, like head, tail, init, last, drop, and take."""

    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = values

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        # if key is of invalid type or value, the list values will raise the error
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

    def __iter__(self):
        return iter(self.values)

    def __reversed__(self):
        return reversed(self.values)

    def append(self, value):
        self.values.append(value)

    def head(self):
        # get the first element
        return self.values[0]

    def tail(self):
        # get all elements after the first
        return self.values[1:]

    def init(self):
        # get elements up to the last
        return self.values[:-1]

    def last(self):
        # get last element
        return self.values[-1]

    def drop(self, n):
        # get all elements except first n
        return self.values[n:]

    def take(self, n):
        # get first n elements
        return self.values[:n]

你有它,一个(勉强)有用的例子,说明如何实现你自己的序列。当然,自定义序列还有更多有用的应用,但其中不少已经在标准库中实现,如CounterOrderedDictNamedTuple.

案例:自定义扑克牌

python
# 定义扑克牌
class Card:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return f'<{self.suit}-{self.rank}>'


# 定义一幅扑克牌
class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = '黑桃 方块 梅花 红桃'.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]


if __name__ == '__main__':
    beer_card = Card('7', '方块')
    print(beer_card.rank, beer_card.suit)

    deck = FrenchDeck()
    print(len(deck))
    print(deck[0].suit, deck[0].rank)
    print(deck[0:-1])

    from random import choice

    print(choice(deck))