【问题标题】:How to define self-made object that can be unpacked by `**`?如何定义可以被`**`解包的自制对象?
【发布时间】:2017-11-09 21:18:38
【问题描述】:

今天我正在学习使用 *** 来解包参数。
我发现liststrtupledict都可以被*解包。
我猜是因为它们都是可迭代的。所以我自己做了一个类。

# FILE CONTENT
def print_args(*args):
    for i in args:
        print i

class MyIterator(object):
    count = 0
    def __iter__(self):
        while self.count < 5:
            yield self.count
            self.count += 1
        self.count = 0

my_iterator = MyIterator()

# INTERPRETOR TEST
In [1]: print_args(*my_iterator)
0
1
2
3
4

有效!但是如何在 python 中创建一个像 dict 这样的 mapping 对象,以便 ** 解包工作呢?有可能这样做吗?除了dict之外,python中是否已经存在另一种mapping对象?


PS: 我知道我可以让一个对象继承自 dict 类以使其成为映射对象。但是是否有一些关键的magic_method__iter__ 来制作没有类继承的映射对象?


PS2: 在@mgilson 的回答的帮助下,我制作了一个可以由** 解包的对象,而无需从当前映射对象继承:

# FILE CONTENT
def print_kwargs(**kwargs):
    for i, j in kwargs.items():
        print i, '\t', j

class MyMapping(object):
    def __getitem__(self, key):
        if int(key) in range(5):
            return "Mapping and unpacking!"

    def keys(self):
        return map(str, range(5))

my_mapping = MyMapping()
print_kwargs(**my_mapping)

# RESULTS
1   Mapping and unpacking!
0   Mapping and unpacking!
3   Mapping and unpacking!
2   Mapping and unpacking!
4   Mapping and unpacking!

请注意,使用** 解包时,映射对象中的键应为str 类型,否则会引发TypeError。

【问题讨论】:

  • 你可以使用collections模块的Abstract Base Classescollections.Mappingcollections.MutableMapping来定义你自己的不继承自dict的映射类。

标签: python dictionary mapping


【解决方案1】:

可以使用任何映射。我建议您从collections.Mappingcollections.MutableMapping1 继承。它们是抽象基类——您提供几个方法,其余的由基类填充。

以下是您可以使用的“frozendict”示例:

from collections import Mapping

class FrozenDict(Mapping):
    """Immutable dictionary.

    Abstract methods required by Mapping are
    1. `__getitem__`
    2. `__iter__`
    3. `__len__`
    """

    def __init__(self, *args, **kwargs):
        self._data = dict(*args, **kwargs)

    def __getitem__(self, key):
        return self._data[key]

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

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

而用法只是:

def printer(**kwargs):
    print(kwargs)

d = FrozenDict({'a': 1, 'b': 2})
printer(**d)

要回答您关于允许解包需要哪些“魔术”方法的问题——仅基于实验——在 Cpython 中,具有__getitem__keys 的类足以允许解包与**。话虽如此,没有保证适用于其他实现(或 CPython 的未来版本)。为了获得保证,您需要实现完整的映射接口(通常在我上面使用的基类的帮助下)。

在 python2.x 中,还有 UserDict.UserDict 可以在 python3.x 中作为collections.UserDict 访问——但是如果你要使用这个,你可以经常只使用subclass from dict

1请注意,从 Python3.3 开始,这些类已移至collections.abc 模块。

【讨论】:

    【解决方案2】:

    首先,让我们定义解包:

    def unpack(**kwargs): 
        """
        Collect all keyword arguments under one hood
        and print them as 'key: value' pairs
        """
        for key_value in kwargs.items(): 
           print('key: %s, value: %s' % key_value)
    

    现在,结构:两个可用的内置选项是collections.abc.Mappingcollections.UserDict。由于还有另一个探索高度可定制的Mapping 类型的答案,我将重点关注UserDict:如果您只需要一个带有一些扭曲的基本dict 结构,UserDict 可以更容易开始。定义后,底层的UserDict 字典也可以作为.data 属性访问。

    1.可以内联使用,像这样:

    from collections import UserDict
    >>> d = UserDict({'key':'value'})
    
    >>> # UserDict makes it feel like it's a regular dict
    >>> d, d.data
    ({'key':'value'}, {'key':'value'})
    

    UserDict 分解为键=值对:

    >>> unpack(**d)
    key: key, value: value
    >>> unpack(**d.data) # same a above
    key: key, value: value
    

    2.如果是子类化,你所要做的就是在__init__中定义self.data。请注意,我使用 (self+other) 'magic' 方法扩展了该类的附加功能:

    class CustomDict(UserDict):
        def __init__(self, dct={}):
            self.data = dct
    
        def __add__(self, other={}):
            """Returning new object of the same type
               In case of UserDict, unpacking self is the same as unpacking self.data
            """
            return __class__({**self.data, **other})
    
        def __iadd__(self, other={}):
            """Returning same object, modified in-place"""
            self.update(other)
            return self
    

    用法是:

    >>> d = CustomDict({'key': 'value', 'key2': 'value2'})
    >>> d
    {'key': 'value', 'key2': 'value2'}
    >>> type(d), id(d)
    (<class '__main__.CustomDict'>, 4323059136)
    

    添加其他字典(或任何mapping 类型)将调用__add__,返回新对象:

    >>> mixin = {'a': 'aaa', 'b': 'bbb'}
    >>> d_new = d + mixin # __add__
    >>> d_new
    {'key': 'value', 'a': 'aaa', 'key2': 'value2', 'b': 'bbb'} 
    >>>type(d_new), id(d_new)
    (<class '__main__.CustomDict'>, 4323059248) # new object
    >>> d # unmodified
    {'key': 'value', 'key2': 'value2'}
    

    使用__iadd__ 就地修改将返回相同的对象(内存中的相同ID)

    >>> d += {'a': 'aaa', 'b': 'bbb'} # __iadd__
    >>> d
    {'key': 'value', 'a': 'aaa', 'key2': 'value2', 'b': 'bbb'}
    >>> type(d), id(d)
    (<class '__main__.CustomDict'>, 4323059136)
    

    顺便说一句,我同意其他贡献者的观点,您也应该熟悉 collections.abc.Mapping 和兄弟类型。对于基本的字典探索,UserDict 具有所有相同的功能,并且不需要您在可用之前覆盖抽象方法。

    【讨论】:

    • 我不太明白2. 语句的含义。你能用更明确的陈述来解释吗?
    • “对这个类 [UserDict] 的需求在很大程度上已经被直接从 dict 子类化的能力所取代(从 Python 版本 2.2 开始提供的功能)。” - docs.python.org/2/library/userdict.html
    • @Moberg -- 这是真的。我发现它设法迁移到 python3.x 的collections 很有趣...我原以为他们会完全删除它。 . .
    • stackoverflow.com/a/8601389/1256112 这正是 OP 所寻找的。​​span>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-19
    • 2021-08-05
    • 1970-01-01
    • 2019-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多