1

Today I'm learning using * and ** to unpack arguments.
I find that both list, str, tuple, dict can be unpacked by *.
I guess because they are all iterables. So I made my own class.

# 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

It works! But how to make a mapping object like dict in python so that ** unpacking works on it? Is it possible to do that? And is there already another kind of mapping object in python except dict?


PS: I know I can make an object inherit from dict class to make it a mapping object. But is there some key magic_method like __iter__ to make a mapping object without class inheritance?


PS2: With the help of @mgilson's answer, I've made an object which can be unpacked by ** without inherit from current mapping object:

# 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!

Be aware, when unpacking using **, the key in your mapping object should be type str, or TypeError will be raised.

Zen
  • 4,381
  • 5
  • 29
  • 56
  • 3
    You can use the `collections` module's [Abstract Base Classes](https://docs.python.org/2/library/collections.html#collections-abstract-base-classes) `collections.Mapping` and `collections.MutableMapping` to define your own mapping classes that don't inherit from `dict`. – martineau Dec 15 '15 at 09:48

2 Answers2

1

Any mapping can be used. I'd advise that you inherit from collections.Mapping or collections.MutableMapping1. They're abstract base classes -- you supply a couple methods and the base class fills in the rest.

Here's an example of a "frozendict" that you could use:

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)

And usage is just:

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

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

To answer your question about which "magic" methods are necessary to allow unpacking -- just based on experimentation alone -- in Cpython a class with __getitem__ and keys is enough to allow it to be unpacked with **. With that said, there is no guarantee that works on other implementations (or future versions of CPython). To get the guarantee, you need to implement the full mapping interface (usually with the help of a base class as I've used above).

In python2.x, there's also UserDict.UserDict which can be accessed in python3.x as collections.UserDict -- However if you're going to use this one, you can frequently just subclass from dict.

1Note that as of Python3.3, those classes were moved to thecollections.abc module.

Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
0

First, let's define unpacking:

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)

Now, the structure: two built-in options available are collections.abc.Mapping and collections.UserDict. As there's another answer exploring highly-customizable Mapping type, I will focus on UserDict: UserDict can be easier to start with if all you need is a basic dict structure with some twist. After definition, underlying UserDict dictionary of is also accessible as .data attribute.

1.It can be used inline, like so:

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'})

Breaking UserDict into key=value pairs:

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

2.If subclassing, all you have to do is to define self.data within __init__. Note that i expanded the class with additional functionality with (self+other) 'magic' methods:

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

Usage is:

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

Adding other dict (or any mapping type) to it will call __add__, returning new object:

>>> 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'}

In-place modification with __iadd__ will return the same object (same id in memory)

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

Btw, i agree with other contributors that you should also be familiar with collections.abc.Mapping and brethren types. For basic dictionary exploration UserDict has all the same features and does not require from you to override abstract methods before becoming usable.

Vlad M
  • 477
  • 3
  • 10
  • I'm not quite understand what does the `2.` statement mean. Could you explain it with more explicit statements? – Zen Dec 16 '15 at 02:02
  • "The need for this class [UserDict] has been largely supplanted by the ability to subclass directly from dict (a feature that became available starting with Python version 2.2)." - https://docs.python.org/2/library/userdict.html – Moberg Dec 16 '15 at 07:23
  • @Moberg -- That's true. I find it interesting that it managed to migrate into `collections` for python3.x... I would have thought they'd just cut it out completely. . . – mgilson Dec 16 '15 at 08:08
  • 1
    http://stackoverflow.com/a/8601389/1256112 that is exactly what OP was looking for. – Vlad M Dec 17 '15 at 08:33