编写一个类,当以 list(c) 和 dict(c) 调用时返回不同的值

ost*_*ach 3 python

我正在尝试实现一个自定义类,该类在调用 as list(c)or时返回不同的值dict(c)。然而,我的印象是既list(c)dict(c)使用c.__iter__()引擎盖下?如果是这种情况,我怎样才能获得不同的行为调用list(c)dict(c)?我知道这是可能的,因为 Python 字典和 Pandas DataFrames 有不同的 hevariours。

例如:

Foo类:
    def __init__(self):
        self._keys = ['a', 'b', 'd', 'd', 'e']
        self._data = [10, 20, 30, 40, 50]

    def __iter__(self):
        对于密钥,zip 中的值(self._keys, self._data):
            yield 键,值

打电话dict(c)我得到我想要的:

>>> f = Foo()
>>> dict(f)
{'a':10,'b':20,'d':40,'e':50}

但是,我无法list(c)打印出键(或值)列表,而是同时获取:

>>> f = Foo()
>>> 列表(f)
[('a', 10), ('b', 20), ('d', 30), ('d', 40), ('e', 50)]

字典的等效代码要清晰得多:

>>> f = {'a':10,'b':20,'c':30,'d':40,'e':50}
>>> dict(f)
{'a':10,'b':20,'c':30,'d':40,'e':50}
>>> 列表(f)
['a', 'b', 'c', 'd', 'e']

Ant*_*ala 5

显然__iter__必须只返回,否则list(f)将无法工作。

Python 文档说明了dict构造函数的以下内容:

如果给出了一个位置参数并且它是一个映射对象,则使用与映射对象相同的键值对创建一个字典。

现在,问题是什么是dict构造函数足够的“映射” ?DataFrame不从任何映射类继承,也不向抽象基类注册。事实证明,我们只需要支持该keys方法:如果传递给dict构造函数的对象有一个名为 的方法keys,则调用该方法以提供可迭代的键[CPython 源代码]。对于每个键,值是通过索引获取的。

dict构造函数执行以下逻辑等效项:

if hasattr(source, 'keys'):
    for k in source.keys():
        self[k] = source[k]
else:
    self.update(iter(source))
Run Code Online (Sandbox Code Playgroud)

使用这个我们得到

class Foo:
    def __init__(self):
        self._keys = ['a', 'b', 'd', 'd', 'e']
        self._data = [10, 20, 30, 40, 50]

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

    def __getitem__(self, key):
        idx = self._keys.index(key)
        return self._data[idx]

    def keys(self):
        return self._keys
Run Code Online (Sandbox Code Playgroud)

测试:

>>> f = Foo()
>>> list(f)
['a', 'b', 'd', 'd', 'e']

>>> dict(f)
{'d': 30, 'e': 50, 'a': 10, 'b': 20}
Run Code Online (Sandbox Code Playgroud)

(从上面的代码可以看出,实际上没有必要继承任何东西)

但是,不能保证所有映射构造函数都以相同的方式运行——其他一些可能会调用items——因此最兼容的方法是实现collections.abc.Mapping它所需的所有方法并从中继承。即足以做

class Foo(collections.abc.Mapping):
    ...
    def __getitem__(self, key):
        idx = self._keys.index(key)
        return self._data[idx]

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

    def __len__(self):
        return len(self._keys)
Run Code Online (Sandbox Code Playgroud)