java mysql 程序员 wordpress 微软 开源 google Ubuntu Android Windows 编程 shell Firefox apache nginx centos php 云计算 Python linux

Python的神奇方法指南:制作自定义序列

很有多种方式可以让你的类表现得像内建序列(字典,元组,列表,字符串等)。 这些是我迄今为止最喜欢的神奇方法了,因为不合理的控制它们赋予了你一种魔术般地让你的类实例整个全局函数数组漂亮工作的方式。 在我们开始讲解这个内容之前,让我们先快速理清需求。

需求

现在我们正在谈论如何创建你自己的序列。 也是什么谈一谈 protocol 了。 protocol 在某些地方跟接口很相似。 接口在其他语言中,是一组给定的方法,而你必须定义它们。 然而,在 Python 中 protocol 是完全非正式的,而且不要求显式声明去实现。 更进一步说,它们更像是准则。

为何我们现在要谈论 protocol? 因为在 Python 中要实现自定义容器类型会涉及使用到这其中某些 protocol。

首先,有一个 protocol 是为定义不可变容器的:为了制作一个不可变容器,你只需要定义 __len__ 和__getitem__(稍后详述)。 可变容器 protocol 要求所有不可变容器增加 __setitem__ 和 __delitem__。 然后,如果你希望你的对象是可迭代的,那你还得定义一个会返回迭代器 iterator 的 __iter__ 方法。 并且这个迭代器必须遵守一个迭代 protocol,也就是要求迭代器有回调方法 __iter__ (返回自身)和 next。

隐藏在容器背后的魔法

已经迫不及待了?以下便是容器使用的神奇魔法:

__len__(self)

返回容器的长度。部分 protocol 同时支持可变和不可变容器

__getitem__(self, key)

定义当某一个 item 被访问时的行为,使用 self[key] 表示法。 这个同样也是部分可变和不可变容器 protocol。 这也可抛出适当的异常: TypeError 当 key 的类型错误,或没有值对应 Key 时。

__setitem__(self, key, value)

定义当某一个 item 被赋值时候的行为,使用 self[key]=value 表示法。 这也是部分可变和不可变容器 protocol。 再一次重申,你应当在适当之处抛出 KeyError 和 TypeError 异常。

__delitem__(self, key)

定义当某一个 item 被删除(例如 del self[key])时的行为。 这仅是部分可变容器的 protocol。在一个无效key 被使用后,你必须抛出一个合适的异常。

__iter__(self)

应该给容器返回一个迭代器。 迭代器会返回若干内容,大多使用内建函数 iter() 表示。 当一个容器使用形如 for x in container: 的循环。 迭代器本身就是其对象,同时也要定义好一个 __iter__ 方法来返回自身。

__reversed__(self)

当定义调用内建函数 reversed() 时的行为。应该返回一个反向版本的列表。

__contains__(self, item)

__contains__ 为成员关系,用 in 和 not in 测试时定义行为。 那你会问这个为何不是一个序列的 protocol 的一部分? 这是因为当 __contains__ 未定义,Python 就会遍历序列,如果遇到正在寻找的 item 就会返回True。

__concat__(self, other)

最后,你可通过 __concat__ 定义你的序列和另外一个序列的连接。 应该从 self 和 other 返回一个新构建的序列。 当调用 2 个序列时 __concat__ 涉及操作符 +

一个例子

在我们的例子中,让我们看一下一个 list 实现的某些基础功能性的构建。 可能会让你想起你使用的其他语言(比如 Haskell)。

class FunctionalList:
    '''类覆盖了一个list的某些额外的功能性魔法,像headtailinitlast,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):
        # 如果key是非法的类型和值,那么list valuse会抛出异常
        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):
        # 获得第一个元素
        return self.values[0]
    def tail(self):
        # 获得在第一个元素后的其他所有元素
        return self.values[1:]
    def init(self):
        # 获得除最后一个元素的序列
        return self.values[:-1]
    def last(last):
        # 获得最后一个元素
        return self.values[-1]
    def drop(self, n):
        # 获得除前n个元素的序列
        return self.values[n:]
    def take(self, n):
        # 获得前n个元素
        return self.values[:n]

通过这个(轻量的)有用的例子你知道了如何实现你自己的序列。 当然,还有很多更有用的应用,但是它们其中的很多已经被标准库实现了,像 Counter, OrderedDict, NamedTuple

延伸阅读

评论