How does one implement custom double star operator (**
) for unpacking, similar to how __iter__
works with single star operator (*
)?
For example:
class PlayerManager(object):
def __init__(self, players=None):
self.players = players or []
# Made up method to support ** operator
def __dict_iter__(self):
for player in self.players:
yield get_steamid(player), player
def print_players(**players):
print(players)
player_manager = PlayerManager([list, of, players])
print_players(**player_manager)
Output:
{
'STEAM_0:0:02201': <Player object at 0x0000000000>,
'STEAM_0:0:10232': <Player object at 0x0000000064>,
'STEAM_0:0:73602': <Player object at 0x0000000128>
}
In a function definition, argument with double asterisks as prefix helps in sending multiple keyword arguments to it from calling environment >>> def function(**arg): for i in arg: print (i,arg[i]) >>> function(a=1, b=2, c=3, d=4) a 1 b 2 c 3 d 4.
The double asterisk is being used in two different contexts: It is used as a exponentiation operator in an arithmetic context. It is used as an extended file match globbing operator from Bash 4, meaning it matches filenames and directories recursively.
Here a single asterisk( * ) is also used in *args. It is used to pass a variable number of arguments to a function, it is mostly used to pass a non-key argument and variable-length argument list.
The asterisk (star) operator is used in Python with more than one meaning attached to it. Single asterisk as used in function declaration allows variable number of arguments passed from calling environment. Inside the function it behaves as a tuple.
As @ShadowRanger says, implement Mapping. Here's an example:
from collections.abc import Mapping
class Foo(Mapping):
def __iter__(self):
yield "a"
yield "b"
def __len__(self):
return 2
def __getitem__(self, item):
return ord(item)
f = Foo()
print(*f)
print(dict(**f))
The program outputs:
a b
{'a': 97, 'b': 98}
Implement the Mapping
ABC. Technically, the language docs don't specify which Mapping
methods are used, so assuming you only need some subset used by the current implementation is a bad idea. All it says is:
If the syntax **expression appears in the function call, expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments. In the case of a keyword appearing in both expression and as an explicit keyword argument, a TypeError exception is raised.
So if you implement the Mapping
ABC, you definitely have the right interfaces, regardless of whether it relies on .items()
, direct iteration and __getitem__
calls, etc.
FYI, on checking, the behavior in CPython 3.5 definitely dependent on how you implement Mapping
(if you inherit from dict
, it uses an optimized path that directly accesses dict
internals, if you don't, it iterates .keys()
and looks up each key as it goes). So yeah, don't cut corners, implement the whole ABC. Thanks to default implementations inherited from the Mapping
ABC and it's parents, this can be done with as little as:
class MyMapping(Mapping):
def __getitem__(self, key):
...
def __iter__(self):
...
def __len__(self):
...
The default implementations you inherit may be suboptimal in certain cases (e.g. items
and values
would do semi-evil stuff involving iteration and look up, where direct accessors might be faster depending on internals), so if you're using it for other purposes, I'd suggest overriding those with optimized versions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With